FooCAD Source Codeimport 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()
}
}