Exit Full View
Up

/Vases/PlantPot.foocad

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

/**
    So many shop-bought decorative plant pots don't fit the non-decorative pots,
    even from the same shop! Grrr. But now we can use 3D printers - Yay!
    
    The "default" size 80x75 fits a good quality pot found in Wilko.
    I print this with 0.2 layer height, 2 shells and 4 solid layers, 10% infill.

Print Notes

    For the largest pots, I used 0.3mm layer height, and "fast" settings.
    This may have made the pots weaker though. There were noticable faults
    in the layer lines. If using fsat settings again, try using higher temperatures.

I've coded information into my custom names, which are also included as text
on the bottom of the pot :

Sizes
    SHOT = Shot glass (39x42)
    XS = Xtra Small (62x55) - Tiny pots (I used them for small cactii)
    S = Small (80x75) - Smaller than normal
    M = Medium (95x90) - "Normal" size pots
    L = Large (150x140) - First printed for Amanda's birthday present
    LL = Larger (150x140) - First printed for Amanda's birthday present
    XL = Extra Large (250x233) - First printed for the big Raindow pot.

    H = Huge (150 x 140) - Old name, but same as "L"

    SQ = Small Square (70x81) - From Wilko
    MQ = Medium Square (93x100) - From Wilko

Modifiers of the basic shape :
    En Edges e.g. n=4 for square pots. Also for round pots in rounded polygons.
    Rn Resolution
    In Decorative Indents
    Vn RevolveResolution - 6ish for a vertical panelled effect.
    
*/

class PlantPot : Model {
     
    @Custom( lines=2 )
    var text = "S1"

    @Custom( required=false )
    var textStyle = TextStyle( BOLD, 10 ).hAlign( HAlignment.CENTER )

    @Custom
    var inside = true

    // Choose which shape to use (1..7)
    // Looks for a shape with an id of potN in plantPot.svg where N is 1..7
    @Custom
    var type = "1"

    // The diameter of the non-decorative pot which is placed inside.
    // You should include a small amount of slack too.
    @Custom
    var potDiameter = 80

    // The height of the non-decorative pot which is placed inside.
    // You should include a small amount of slack too.
    @Custom
    var potHeight = 75

    // The thickness of the sides and the base.
    // For large pots, increase the number of shells in the slic3r settings!
    @Custom
    var thickness = 3.0

    @Custom
    var baseThickness = 3.0

    // Set to zero for no decorative "indents".
    // NOTE, this was only designed for type="1", but may work with others.
    // Will only work when sides=0
    @Custom
    var indentCount = 0


    // Use 0 for a circle, 4 or more for a 'sided' pot.
    // Note the corners are rounded, so beware when using square pots.
    @Custom
    var sides = 0

    // The default resolution is 1.0. Larger for blocky prints
    @Custom
    var resolution = 1.0

    // How much to scale the resolution by when performing the revolve
    // The profile keeps the same resolution.
    // Set this to something like 6 for lots of vertical lines -
    // similar to setting sides=12, but without rounded corners.
    @Custom
    var revolveResolution = 1.0

    @Custom
    var lip = 0.0
    
    @Custom
    var holes = 0

    @Custom
    var showInner = true

    // Helps debugging, by visualasing the "inner" non-decorative pot
    // This will NOT be printed.
    fun pot() : Shape3d {
        return if (sides == 4) {
            println( "Square pot width : ${potDiameter}" )
            ExtrusionBuilder.drum(
                Square( potDiameter*49/67).center().rotate(45),
                Square( potDiameter ).center().rotate(45).roundAllCorners(2),
                potHeight
            )
                
        } else {

            PolygonBuilder().apply {
                moveTo( 0,0 )
                lineTo( 0, potHeight )
                lineTo( potDiameter/2, potHeight )
                lineTo( potDiameter/2 * 44 / 59, 0)
            }.build().revolve()

        }.translateZ( baseThickness+0.01 ).color( "Gainsboro" )
    }

    fun make3d( profile : Shape2d ) : Shape3d {
       
        val points = profile.firstPath.points
        val baseX = points[1].x // - points[0].x

        var withoutBase : Shape2d = Path2d( points.subList( 1, points.size() ), false )
            .thickness(thickness).toOriginY()
        if (lip > 0) {
            withoutBase += lip(profile)
        }

        var shellP : Shape2d = withoutBase + Square( baseX + thickness, baseThickness )

        if (sides == 0) {

            Quality.minimumSize *= revolveResolution
            Quality.minimumAngle *= revolveResolution
            val result = shellP.revolve()
            Quality.minimumSize /= revolveResolution
            Quality.minimumAngle /= revolveResolution

            return result
        

        } else {
           
            val deltaX = withoutBase.corner.x
            val radius = baseX / Degrees.cos(180/sides)
            // Hmm. Not sure why we need to scale by -1. A Bug in FooCAD I think!
            val base = Circle( radius ).sides(sides).scale(-1).roundAllCorners(1,40~/resolution~/sides)

            return ExtrusionBuilder.followShape(
                base,

                withoutBase.toOrigin().mirrorX()
            ) +
            base.offset(thickness).extrude( baseThickness )
        
        }
    }

    fun lip( profile : Shape2d ) : Shape2d {
        val point = profile.toOrigin().firstPath[profile.firstPath.points.size()-1]

        val lipP = Circle( thickness/2 ).hull(
            Circle( lip/2 ).translate( lip/2-thickness/2, lip*1.2 )
        ).translateX( -thickness/2 )

        return lipP
            .translate(point)
            .translate(thickness/2,thickness/2)
    }

    fun buildVase(profile : Shape2d, scale2 : Vector2) : Shape3d {

        var scale1 = scale2.x
        val scale3 = Vector3( scale2.x, scale2.x, scale2.y )

        var vase : Shape3d = make3d( profile )

        if ( indentCount > 0 ) {
            /*
            var outside = make3d(
                profile
                    .offset(1)
                    .recalcBoundingBox()
                    .toOrigin()
                    .translate(0.01,0).scale( scale2 )
            )
            */
            val outside = Hull3d(vase)
            val bite = Sphere( 37 )
                .scale(1,1,1.3)
                .rotateY(10)
                .translate(72,0,40).repeatAroundZ( indentCount )
                .scale( scale3 )
            val hole = Sphere( 37 - thickness *80 / potDiameter )
                .scale(1,1,1.3)
                .rotateY(10)
                .translate(72,0,40).repeatAroundZ( indentCount )
                .scale(scale3)
    
            vase = vase + (outside / bite) - hole
        }
            
        val text3d = if (inside) {
            Text( text, textStyle )
                .center()
                .extrude( baseThickness )
                .bottomTo( baseThickness - textThickness )
        } else {
            Text( text, textStyle )
                .mirrorX()
                .center()
                .extrude( baseThickness )
                .bottomTo( baseThickness - textThickness )
        }

        println("Inner pot size : ${pot().size}")

        return if (showInner) {
            vase - text3d + pot().previewOnly()
        } else {
            vase - text3d
        }
         
        
    }

    @Custom
    var textThickness = 0.8

    override fun build() : Shape3d {
        
        Quality.increase(3)

        var scale2 = Vector2( potDiameter / 80, potHeight / 75 )
        
        // Try to auto adjust for the rounded corners.
        if ( sides == 4 ) {
            val bodge = 1.04
            scale2 *= Vector2( bodge, 1.0)
        }

        val profile = SVGParser().apply{ scale=scale2 }.parseFile( "plantPot.svg" )
            .shapes[ "pot$type" ].toOrigin()
        
        var result = buildVase( profile, scale2 )

        if (sides == 4) {
            result = result.rotateZ(45)
        }

        if ( holes > 0 ) {
            if (holes == 1) {
                val hole = Cylinder( baseThickness + 1, potDiameter / 30 )

                result = result - hole
            } else {
                val hole = Cylinder( baseThickness + 1, potDiameter / 30 ) +
                    Cube( potDiameter, potDiameter/30, baseThickness/4 ).centerY()
                    
                result = result - hole.translateX(potDiameter*0.25).repeatAroundZ( holes )
            }
        }

        // Remove one slash before 'Cube', to take a slice, so that you can see if the pot
        // will fit.
        return result // Cube( 1000, 1, 1000 ).center().rotateZ(60).also()
    }
}