import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.* import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.* class PlantPot : AbstractModel() { @Custom( lines=2 ) var text = "S1" @Custom 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 ) return 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 ) } return Cube(0) } 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() } }