Exit Full View
Up

/Garden/SimplePlantPot.foocad

SimplePlantPot

A simple plant pot, with thin walls, and a thicker rim for strength. Fully customisable. You can optionally print "feet", and glue them to the bottom, to help water drainage. There is also a matching "saucer" to catch the water.

I'm fed up of having lots of slightly different size plant pots, that do not stack easily. Shops keep changing suppliers, so I cannot buy the same style pots that I bought a decade ago!

With a 3D printer, this problem dissappears, because I am in control of the size, and not at the mercy of shops.

Sizes

Default : 67x80 52 base

The "standard" small sized pots. Wilko sells thin "disposable" pots of this size. Fits 5x3 inside a gravel tray, loosely, poking way over the top.

medium : 90x95 70 base

The "standard" sized pots. Wilko sells thin "disposable" pots of this size.

module : 72x50 52 base

Fits 5x3 inside a standard gravel tray with little slack.

smallModule : 43x50 ? base

FooCAD Source Code
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
import static uk.co.nickthecoder.foocad.layout.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.Layout3d.*

class SimplePlantPot : Model {
    
    // "standard" Wilko sizes : Small67x80.  Medium=90x100
    @Custom( about="Exterior size of the pot. Does not include the feet thickness" )
    var size = Vector2( 67, 80 )

    @Custom( about= "Exterior width of the bottom" )
    var bottomWidth = 52

    @Custom( about="The pot is either a rounded square or a circle" )
    var square = true

    @Custom( about="Ignored when square=false" )
    var cornerRadius = 10

    @Custom( about="The thickness of each layer that make up the walls" )
    var wallThickness = 0.8
 
    @Custom
    var baseThickness = 1.2

    @Custom( about="The thickness of the rim (does not include the wallThickness" )
    var rimThickness = 2.0

    @Custom( about="The height of the rim (does not include the 45 degree section" )
    var rimHeight = 2.0

    @Custom( about="The radius of the holes in the base")
    var holeRadius = 4.0

    @Custom( about="The number of holes in the base. Must be 5 for square pots" )
    var holeCount = 5

    @Custom( about="The thickness of the feet piece")
    var feetThickness = 3.0    

    @Custom( about = "The total height of the saucer, including the baseThickness" )
    var saucerHeight = 10

    @Custom( about = "The gap between the edge of the pot and the inside of the saucer" )
    var saucerGap = 1.0

    fun slopeX() = (size.x - bottomWidth)/2 //Degrees.sin( slopeAngle ) * size.y

    fun slopeAngle() = Degrees.atan( (size.x - rimThickness*2 - bottomWidth)/2 / size.y )

    fun bottom() : Shape2d {
        println( "Slope angle = ${slopeAngle()}")
        val slopeX : double = slopeX()
        return if (square) {
            Square( bottomWidth ).center().roundAllCorners( cornerRadius )
        } else {
            Circle( bottomWidth/2 ).sides(60)
        }
    }

    @Piece
    fun pot() : Shape3d {
        val slopeX : double = slopeX()
        val top = if (square) {
            Square( size.x - rimThickness*2 ).center().roundAllCorners( cornerRadius )
        } else {
            Circle( size.x/2 - rimThickness ).sides( 60 )
        }

        val bottom : Shape2d = bottom()

        val pot = ExtrusionBuilder().apply {
            crossSection( bottom )
            forward( size.y )
            crossSection( top )
            crossSection( -wallThickness )
           
            forward( -size.y + baseThickness )
            // Foo ensures that the wall thickness is constant. Without it, the wall would be thicker at
            // the bottom.
            val foo = Degrees.sin( slopeAngle() ) * baseThickness
            crossSection( bottom.offset( -wallThickness + foo ) )
            
        }.build()

        val rim = ExtrusionBuilder().apply {
            forward( size.y - rimHeight - rimThickness )
            val foo = slopeX * ( ( rimHeight + rimThickness) / size.y )
            crossSection( top.offset( -foo ) )
            forward( rimThickness )
            crossSection( rimThickness )
            forward( rimHeight )
            crossSection( foo )
            crossSection( top.offset( -wallThickness) )
        }.buildClosed()
        
        val holeP = Circle( holeRadius ).sides(8).rotate(45/2)
        val holeOffset = -holeRadius * 2 + if (square) size.x* 0.38 else size.x * 0.31
        var holesP = if (square) {
             if (holeCount > 4) {
                 holeP +
                 holeP.translateX( holeOffset ).repeatAround( holeCount-1 ).rotate(45)
            } else {
                 holeP.translateX( holeOffset ).repeatAround( holeCount ).rotate(45)
            }
        } else {
             holeP.translateX( holeOffset ).repeatAround( holeCount )
        }
        val holes = holesP.extrude( baseThickness*3 )
            //.translateZ(0.4)
            //.center()
    

        return pot - holes + rim
    }

    @Piece
    fun feet() : Shape3d {

        val slopeX : double = slopeX()
        val bottom : Shape2d = bottom()

        val ring = bottom - bottom.offset( -bottom.size.x * 0.07 )
        val plus = Square( ring.size.x*2, bottom.size.x * 0.1 ).center().rotate(90).also()

        val scale = 1-feetThickness/2/size.y
        val feet = ring.extrude( feetThickness, scale ) -
            plus.extrude( feetThickness ).translateZ( feetThickness / 2 )

        return feet.color("Orange")
    }

    @Piece
    fun saucer() : Shape3d {
        val bottom : Shape2d = bottom().offset(wallThickness + saucerGap )
        println( "Bottom size : ${bottom.size}" )
        val saucer = ExtrusionBuilder().apply {
            crossSection( bottom )
            forward( saucerHeight )
            crossSection( Degrees.sin( slopeAngle() ) * saucerHeight )
            crossSection( - wallThickness )
            forward( -saucerHeight + wallThickness )
            crossSection( Degrees.sin( slopeAngle() ) * (-saucerHeight+baseThickness) )
        }.build()

        return saucer
    }

    var gridAcross = 4
    var gridDown = 3
    var gridThickness = 1.2
    var gridHeight = 6

    @Piece
    fun grid() : Shape3d {
        val pot : Shape3d = pot()

        val inside = bottom().offset(1)
        val profile = inside.offset( gridThickness ) - inside
        val all = profile
            .repeatX( gridAcross, pot.size.x )
            .repeatY( gridDown, pot.size.y )
            .extrude( gridHeight )
            .centerXY()

        val yLine = Cube( gridThickness, gridDown * inside.size.y, gridHeight )
            .translateX( profile.size.x - gridThickness ).also()
            .repeatX( gridAcross, pot.size.x )
            .centerXY()
        val xLine = Cube( gridAcross * inside.size.x, gridThickness, gridHeight )
            .translateY( profile.size.y - gridThickness ).also()
            .repeatY( gridDown, pot.size.y )
            .centerXY()

        val pots = pot.tileX(gridAcross).tileY(gridDown).previewOnly().centerXY()
        return all + xLine + yLine //+ pots
    }

    override fun build() : Shape3d {
        return saucer().color("Green").topTo(-14) +
            feet().mirrorZ().topTo(0) +
            pot()    
    }
}