/Garden/SimplePlantPot.foocad

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
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
class SimplePlantPot : AbstractModel() {
// "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
}
@Piece( printable = false )
override fun build() : Shape3d {
return saucer().color("Green").topTo(-14) +
feet().mirrorZ().topTo(0) +
pot()
}
}

