import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.* import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.* import static uk.co.nickthecoder.foocad.circular.v1.Circular.* 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.* import uk.co.nickthecoder.foocad.threaded.v2.* class BirdSeedContainer : Model { @Custom var diameter = 120 @Custom var height = 250 @Custom var throatDiameter = 40 @Custom var wallThickness = 1.8 @Custom var baseThickness = 2.0 @Custom var round = 26 @Custom var bodyFillet = 20 @Custom var neckHeight = 36 @Custom var capText = "" val capHeight = 14 val baseHeight = 14 meth capThread() = Thread( throatDiameter + 8, 3 ) .rodChamfer(1) meth baseThread() = Thread( diameter - 6, 3 ) .rodChamfer(1) @Piece @Slice( topSolidLayers=10, bottomSolidLayers=10, perimeters=4 ) // Solid! meth cap() : Shape3d { val thread = capThread() val solid = Circle( throatDiameter/2 + 6 ) .cup( capHeight, 5 ).baseThickness(baseThickness) .outsideBottom( ProfileEdge.roundedChamfer(3) ) .insideBottom( Chamfer(baseThickness) ) .outsideTop( Chamfer(1) ) val hole = thread.threadedHole( solid.top - baseThickness*1.5 ) .chamferStart(false) .chamferEnd(true) .bottomTo( baseThickness*1.5 ) val text = Text( capText ).center().mirrorX() .smartExtrude( 0.6 ) .bottom( Chamfer(0.2).reverse() ) .bottomTo( -0.01 ) return solid - hole - text } @Piece @Slice( topSolidLayers=10, bottomSolidLayers=10, perimeters=4 ) // Solid! meth base() : Shape3d { val thread = baseThread() val solid = Circle( diameter/2 ) .cup( baseHeight, 7 ).baseThickness(baseThickness) .outsideBottom( ProfileEdge.roundedChamfer(5) ) .insideBottom( Chamfer(baseThickness) ) .outsideTop( Chamfer(1) ) val hole = thread.threadedHole( solid.top - baseThickness*1.5 ) .chamferStart(false) .bottomTo( baseThickness*1.5 ) return solid - hole } @Piece @Slice( brimWidth=5, brimWidthInterior=5, seamPosition="random" ) meth body() : Shape3d { val bodyHeight = height - baseHeight - capHeight - neckHeight val baseThread = baseThread() val base = baseThread.threadedRod( baseHeight-1 ) .chamferTop( false ) val insideBase = Circle( baseThread.coreRadius() - wallThickness*2 ) .smartExtrude( baseHeight-1 + 0.02 ) .bottom( Chamfer(0.5).reverse() ) .top( Chamfer(wallThickness+1).reverse() ) .bottomTo( -0.01 ) val circle = Circle( diameter/2 ) val squarish = Square( diameter ).center().roundAllCorners(round, 20) val bottomFillet = (diameter/2-round)*1 + 2 val middle = squarish .smartExtrude( bodyHeight ) .top( roundedChamfer( bodyFillet ) ) .bottom( roundedChamfer(bottomFillet) ) .bottomTo( base.top - 2 ) val insideMiddle = squarish.offset(-wallThickness) .smartExtrude( bodyHeight ) .top( roundedChamfer( bodyFillet ) ) .bottom( roundedChamfer(bottomFillet) ) .bottomTo( base.top - 2 ) val transition = circle .smartExtrude( bottomFillet ) .bottom( ProfileEdge.roundedChamfer(9) ) .bottomTo( middle.bottom ) val insideTransition = circle.offset(-wallThickness) .smartExtrude( bottomFillet ) .bottom( ProfileEdge.roundedChamfer(11) ) .bottomTo( middle.bottom ) val capThread = capThread() val neckSteps = 20 val neck = ExtrusionBuilder().apply { for ( step in 0 until neckSteps ) { val t = step/neckSteps crossSection( neckShape(t) ) forward( neckHeight/neckSteps ) } crossSection( neckShape(1) ) forward(2) crossSection( Circle(capThread.coreRadius()) ) }.build() .bottomTo( middle.top ) val insideNeck = ExtrusionBuilder().apply { val steps = 10 for ( step in 0 until neckSteps ) { val t = step/neckSteps crossSection( neckShape(t).offset(-wallThickness) ) forward( neckHeight/neckSteps ) } forward(2) crossSection( neckShape(1).offset(-wallThickness) ) }.build() .bottomTo( middle.top ) val forCap = capThread.threadedRod( capHeight-1 ) .bottomTo( neck.top-1 ) val insideForCap = Circle( throatDiameter/2 ) .smartExtrude( capHeight ) .bottomTo( neck.top - 1.01 ) // insides val insides = insideMiddle + insideTransition + insideBase + insideNeck + insideForCap return base + middle + transition + neck + forCap - insides } meth neckShape( t : double ) : Shape2d { val startDiameter = diameter - bodyFillet val endDiameter = throatDiameter + wallThickness*2 val s = Math.pow(Math.sin( t*Math.PI/2 ), 1.1) val d = startDiameter*(1-s) + endDiameter*s val startRound = round - bodyFillet/2 + 0.5 val endRound = endDiameter/2 - 0.1 val round = startRound *(1-t) + endRound*t return Square( d ).center().roundAllCorners(round, 20) } @Piece meth tray() : Shape3d { val height = 10 val diameter = base().size.x + 0.4 val thickness = 2 val clearance = 0.2 val margin = 2 val circles = Circle( diameter/2 + clearance ) .repeatX(3, diameter + thickness*2 + clearance*2) .repeatY(2, diameter + thickness*2 + clearance*2) .center() val outside = circles.boundingSquare().offset(thickness + margin) val fill = (outside.roundAllCorners(20) - circles).toPolygon() .smartExtrude( height ) .outsideBottom( Chamfer(1) ) .top( Chamfer(1) ) return fill } @Piece( printable=false ) override fun build() : Shape3d { val base = base() val body = body().bottomTo( base.top ).bottomTo( baseThickness*1.5 ).color("Green") val cap = cap().rotateX(180).topTo( body.top + baseThickness*1.5 ) return base + body + cap } }