/Boxes/SmoothOutsideBox.foocad
A box, with thin walls, but thicker there the lip of the lid meets the main body.
The box can be built in 2, 3 or more parts. You will always need 1 "top" and 1 "bottom", and you can add 0 or more "middle" pieces.
"slack" is quite and important @Custom parameter, and will need fine tuning for your partiuclar printer. Too small, and the lid will jam on too tightly. Too large, and the lid will be horribly loose. Only the "top" piece uses "slack", so if you need to adjust it, only the "top" piece needs to be re-printed.
The shape of the box is arbitrary (override profile() method).
To get all the other profile sizes (for the lip, inside, chamfer etc), I currently use "scale". This ensures it works with any profile you want, but has the disadvantage that it doesn't produce perfectly consistant width lips.
The "better" solution would be to use Shape2d.offset(n), but this can produce non 1-to-1 correspondance between the points that make up sucessive profiles, and ExtrusionBuilder does a very bad job sometimes! A future version of FooCAD may fix this, and then Shape2d.offset will be better than Shape2d.scale.
import static uk.co.nickthecoder.foocad.chamferedExtrude.ChamferedExtrude.* import static uk.co.nickthecoder.foocad.layout.Layout2d.* import static uk.co.nickthecoder.foocad.layout.Layout3d.* class SmoothOutsideBox : Model { @Custom var height = 40 @Custom var topHeight = 10 @Custom var middleHeight = 30 @Custom var topChamfer = 2.0 @Custom var bottomChamfer = 2.0 @Custom var lipHeight = 8.0 @Custom var lipThickness = 2.0 @Custom var wallThickness = 1.0 @Custom var baseThickness = 2.0 @Custom var slack = 0.3 fun profile() : Shape2d = Square( 70 ).center() fun inset( amount : double ) : Shape2d { val profile : Shape2d = profile() return profile.scale( (profile.size.x - amount * 2) / profile.size.x, (profile.size.y - amount * 2) / profile.size.y ) } @Piece fun top() : Shape3d { val profile : Shape2d = profile() val chamfered = inset( topChamfer ) val lipA = inset( slack + lipThickness ) val lipB = inset( slack + lipThickness*2 ) val inside = inset( wallThickness ) val insideChamfer = inset( wallThickness + topChamfer ) return ExtrusionBuilder().apply { joinStrategy = OneToOneJoinStrategy.instance crossSection( chamfered ) forward( topChamfer ) crossSection( profile ) forward( topHeight - topChamfer ) crossSection() crossSection( lipA ) forward( lipHeight ) crossSection() crossSection( lipB ) forward( -lipHeight - lipThickness/2 ) crossSection() val foo = - topHeight + lipThickness*2 + slack - wallThickness + lipThickness/2 + baseThickness + topChamfer if (foo < 0) { forward( - lipThickness*2 - slack + wallThickness ) crossSection(inside) forward( foo ) } crossSection() if ( foo < 0 ) { forward( -topChamfer ) } else { //forward( foo - topChamfer ) } crossSection( insideChamfer ) }.build().mirrorX() } @Piece fun middle() : Shape3d { if ( middleHeight <= 0 ) return Cube(0) val profile : Shape2d = profile() val bottomLipA = inset( slack + lipThickness ) val bottomLipB = inset( slack + lipThickness*2 ) val inside = inset( wallThickness ) val topLipA = inset( lipThickness ) return ExtrusionBuilder().apply { joinStrategy = OneToOneJoinStrategy.instance crossSection( profile ) forward( middleHeight ) crossSection() crossSection( bottomLipA ) forward( lipHeight ) crossSection() crossSection( bottomLipB ) forward( -lipHeight - lipThickness/2 ) crossSection() forward( -lipThickness * 2 - slack + wallThickness ) crossSection( inside ) forward( - middleHeight + lipThickness*2 + slack - wallThickness + lipHeight + lipThickness/2 - lipHeight + lipHeight + lipThickness - wallThickness // Matches below ) crossSection() forward( -lipThickness + wallThickness ) crossSection( wallThickness - lipThickness ) forward( -lipHeight ) crossSection() }.buildClosed().mirrorX() } @Piece fun bottom() : Shape3d { val profile : Shape2d = profile() val chamfered = inset( bottomChamfer ) val lipA = inset( lipThickness ) val inside = inset( wallThickness ) val insideChamfer = inset( wallThickness + bottomChamfer ) return ExtrusionBuilder().apply { joinStrategy = OneToOneJoinStrategy.instance crossSection( chamfered ) forward( bottomChamfer ) crossSection( profile ) forward( height - bottomChamfer ) crossSection() crossSection( lipA ) forward( -lipHeight ) crossSection() forward( -lipThickness + wallThickness ) crossSection( inside ) forward( -height - wallThickness + lipThickness + lipHeight + bottomChamfer + baseThickness ) crossSection() forward( -bottomChamfer ) crossSection( insideChamfer ) }.build() } @Piece override fun build() : Shape3d { val gap = 10 return bottom() .centerXY().color("Blue") + middle() .rotateX(180) .translateZ(height + middleHeight + slack + gap) .centerXY().color("Silver") + top() .rotateX(180).rotateZ(180) .translateZ(height + middleHeight + topHeight + slack*2 + gap*2 ) .centerXY().color("Yellow") } }