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