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