FooCAD Source Codeimport static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
class PuzzleBox : Model {
@Custom
var width = 70.0 // Internal size
@Custom
var depth = 60.0 // Internal size
@Custom
var middleHeight = 22.0 // Internal size
@Custom
var topHeight = 10.0 // Internal size
@Custom
var bottomHeight = 30.0 // Internal size
@Custom
var radius = 6.0 // Radius of the INSIDE. Other radii are calculated.
@Custom
var radiusSides = 4.0
@Custom
var thickness = 4.0 // Thickness of the top (sides and top/bottom)
@Custom
var patternThickness = 0.6 // he depth of the pattern cut into the top/bottom
@Custom
var lipThickness = 2.0 // Should probably be half of thickness (or close to it)
@Custom
var lipHeight = 6.0 // The height of the lip on the bottom piece.
// I created small prototypes to find the best value for this.
// It is the additional space in the top part for the bottom's lip
// to fit into. I wanted an interferance fit.
@Custom
var lipSlack = 0.3
@Custom
var chamfer = 2.0 // Chamfer of the bottom and top edges.
/**
For large boxes, it is important to add ears to the corners, so
that the box doesnt lift off of the print bed.
For very large boxes, maybe ears along the edges are also
needed (especially when using ABS), but this model doesn't
support ears along the edges. So either improves the model,
or add extra ears anually in your slicer.
NOTE, I also tape the ears to the bed with masking tape after they
have been printed (i.e. after the first layer is complete)
*/
@Custom
var cornerEars = 0.0
fun createProfile( offset : double ) =
Square( width + offset*2, depth + offset*2 )
.center()
.roundAllCorners( radius + offset, radiusSides )
fun createContent( height : double ) =
Square( width -1, depth -1 ).roundAllCorners( radius - 0.5 )
.extrude( height )
@Piece
fun top() = buildPart( "top" )
@Piece
fun middle() = buildPart( "middle" )
@Piece
fun bottom() = buildPart( "bottom" )
@Piece
fun view() = buildPart("view")
override fun build() = buildPart("exploded")
fun buildPart( part : String) : Shape3d {
val contentCubes =
createContent( bottomHeight-1).translateZ(1) +
createContent( middleHeight).translateZ( bottomHeight + 1) +
createContent( topHeight-1).translateZ( bottomHeight + middleHeight + 2)
val contents = contentCubes
.centerXY()
.translateZ(thickness)
.color("Orange")
val inside = createProfile( 0 )
val outside = createProfile( thickness )
val outsideChamfer = createProfile( thickness - chamfer)
val outsideLip = createProfile( lipThickness )
val slackLip = createProfile(lipThickness - lipSlack )
val top : Shape3d = ExtrusionBuilder().apply {
crossSection( outsideChamfer )
forward( chamfer )
crossSection( outside )
forward( topHeight + thickness - chamfer )
crossSection( outside )
crossSection( outsideLip )
forward( -lipHeight )
crossSection( outsideLip )
crossSection( inside )
forward( -topHeight + lipHeight)
crossSection( inside )
}.build().color( "Blue" )
val bottom : Shape3d = ExtrusionBuilder().apply {
crossSection( outsideChamfer )
forward( chamfer )
crossSection( outside )
forward( bottomHeight + thickness - chamfer )
crossSection( outside )
crossSection( slackLip )
forward( lipHeight - 1 )
crossSection( slackLip )
crossSection( inside )
forward( -bottomHeight - lipHeight + 1 )
crossSection( inside )
}.build().color( "Blue" )
val middleSolid = ExtrusionBuilder().apply {
crossSection( outside )
forward( middleHeight )
crossSection( outside )
crossSection( slackLip )
forward( lipHeight )
crossSection( slackLip )
}.build()
.color( "Blue" ).brighter()
val middleHole = ExtrusionBuilder().apply {
forward(-0.1)
crossSection( outsideLip )
forward(0.1 + lipHeight + 1 )
crossSection( outsideLip )
forward( thickness ) // Chamfer so that we don't need support material
crossSection( inside )
forward( middleHeight - thickness -1 + 0.1)
crossSection( inside )
}.build()
val middle = middleSolid - middleHole
val view = contents +
bottom +
middle.translateZ( bottomHeight + thickness + 1 ).color("Yellow") +
top
.rotateX(180)
.toOriginZ()
.translateZ( bottomHeight + middleHeight + thickness + 2 )
val pattern : Shape3d = pattern()
.extrude( patternThickness )
.translateZ( -0.01)
.color( "WhiteSmoke" )
val inspection = Cube( 600, 5, 600 ).center() +
Cube( 5, 400, 400 ).center() +
Cube(400, 1, 400).centerY().rotateZ(45)
val ears = Circle( cornerEars )
.translate(
width/2 + cornerEars*0.5 - radius/2,
depth/2 + cornerEars*0.5 - radius/2
)
.mirrorX().also()
.mirrorY().also()
.extrude( 0.2 )
return if (part == "top" ) {
top - pattern + ears
} else if (part == "middle" ) {
middle + ears
} else if (part == "bottom" ) {
bottom - pattern + ears
} else if (part == "view" ) {
view
} else if (part == "exploded" ) {
bottom.translateX( width +10).centerXY() +
//contents +
middle.translateZ(bottomHeight + 20).centerXY() +
top.rotateX(180).translateZ( bottomHeight + middleHeight + topHeight + 50).centerXY()
} else {
view / inspection
}
}
@Custom
var pattern = "circles2"
@Custom
var patternSize = 7.0
fun pattern() : Shape2d {
return if (pattern == "striped") {
Square( width *3, patternSize ).tileY( width / patternSize, patternSize ).center().translateY(patternSize)
} else if (pattern == "striped2" ) {
Square( width *3, patternSize ).tileY( width / patternSize, patternSize ).center()
} else if (pattern == "diagonal" ) {
Square( width *3, patternSize ).tileY( width / patternSize, patternSize ).center().translateY(patternSize).rotate(45)
} else if (pattern == "diagonal2" ) {
Square( width *3, patternSize ).tileY( width / patternSize, patternSize ).center().rotate(45)
} else if (pattern == "spots" ) {
val scale = 0.5
Circle( patternSize )
.tileX( Math.floor(0.25 * width / patternSize), patternSize*2 )
.tileY( Math.floor(0.25 * depth / patternSize), patternSize*2 )
.translate( patternSize * 2, patternSize * 2 ).also()
.center()
} else if ( pattern == "diamond" ) {
val count = width ~/ patternSize
val result = listOf<Shape2d>()
for ( i in 0 until count ) {
result.add(
Square( (i*2+1) * patternSize ).center().rotate(45) -
Square( (i*2) * patternSize ).center().rotate(45)
)
}
Union2d( result )
} else if ( pattern == "diamond2" ) {
val count = width ~/ patternSize
val result = listOf<Shape2d>()
for ( i in 1 until count ) {
result.add(
Square( (i*2) * patternSize ).center().rotate(45) -
Square( (i*2-1) * patternSize ).center().rotate(45)
)
}
Union2d( result )
} else if (pattern == "circles" ) {
val count = 0.4 * Math.sqrt( width * width + depth * depth ) ~/ patternSize
val result = listOf<Shape2d>()
for ( i in 0 until count ) {
result.add(
Circle( (i*2+1) * patternSize ) - Circle( (i*2) * patternSize )
)
}
Union2d( result )
} else if (pattern == "circles2" ) {
val count = 0.4 * Math.sqrt( width * width + depth * depth ) ~/ patternSize
val result = listOf<Shape2d>()
for ( i in 1 until count ) {
result.add(
Circle( i*2 * patternSize ) - Circle( (i*2-1) * patternSize )
)
}
Union2d( result )
} else {
Square( 1 ).translateX( 1000 )
}
}
}