/GardenFurniture/Hardware/BoxConstruction.foocad

Joining pieces to make box frames from wood or other rigid materials.
Some piece types have multiple versions (with suffixes A or B). These will be identical when the wood has a square cross-section.
Some pieces are chiral, so use the Mirror Post-Processing extension as required.
By default the pieces have a hole for a countersink screw. To omit the holes, set screwHole=Cube(0)
Corners have an option to tilt one of the struts; the angles are measured relative to a "normal" 90° corner.
First used by Bike/Box2.foocad
import uk.co.nickthecoder.foocad.extras.v1.*
import static uk.co.nickthecoder.foocad.extras.v1.Extras.*
import uk.co.nickthecoder.foocad.compound.v1.*
import uk.co.nickthecoder.foocad.smartextrusion.v1.*
import static uk.co.nickthecoder.foocad.smartextrusion.v1.SmartExtrusion.*
import uk.co.nickthecoder.foocad.screws.v3.*
class BoxConstruction : Model {
@Custom( about="The width and thickness of the wooden struts" )
var crossSection = Vector2( 20, 16 )
@Custom( about="Radius of the rounding on the wooden struts" )
var insideRadius = 1
@Custom( about="Extra height to support the corners" )
var cornerExtra = 20
@Custom( about="Extra height to support angle braces" )
var angleExtra = 10
@Custom( about="Wall thickness" )
var thickness = 3.0
@Custom
var chamfer = 1
@Custom
var holeChamfer = 0.4
var screwHole : Shape3d = Countersink().depth(6)
meth inside() = Square( crossSection ).center().roundAllCorners( insideRadius )
// length is the length of wood that fits in. The piece is taller by jointThickness.
meth endStop( length : double ) : Shape3d {
val inside = inside()
val outside = inside.offset( thickness )
.roundAllCorners( chamfer )
val solid = outside.smartExtrude( length + thickness )
.bottom( Chamfer( chamfer ) )
.top( Chamfer( chamfer ) )
val hole = inside.smartExtrude( length + 0.01 )
.top( Chamfer( holeChamfer ).reverse() )
.translateZ( thickness )
return solid.remove(hole)
}
meth through( length : double ) : Shape3d {
val inside = inside()
val outside = inside.offset( thickness )
.roundAllCorners( chamfer )
val solid = outside.smartExtrude( length )
.bottom( Chamfer( chamfer ) )
.top( Chamfer( chamfer ) )
val hole = inside.smartExtrude( length + 0.02 )
//.edges( Chamfer( holeChamfer ).reverse() )
.translateZ( -0.01 )
return solid.remove(hole)
}
@Piece
meth jointA() : Shape3d {
val up = endStop( crossSection.x + thickness + cornerExtra ).toOrigin()
val right = endStop( crossSection.x + thickness + cornerExtra ).toOrigin().rotateY(90).bottomTo(0)
val holes = Compound().apply {
+ screwHole.rotateX(-90)
.translateX( crossSection.x/2 + thickness )
.translateY( crossSection.y + thickness*2 )
.translateZ( up.top - cornerExtra/2 )
+screwHole.rotateX(-90)
.translateX( right.right - cornerExtra/2 )
.translateZ( crossSection.x/2 + thickness )
.translateY( up.back )
}.build()
return up and right remove holes
}
@Piece
meth jointB() : Shape3d {
val up = endStop( crossSection.x + thickness + cornerExtra ).toOrigin()
val forward = endStop( crossSection.y + thickness + cornerExtra )
.rotateZ(90).rotateX(-90).toOrigin()
val holes = Compound().apply {
+ screwHole.rotateY(90)
.translateX( forward.right )
.translateY( forward.back - cornerExtra / 2 )
.translateZ( forward.top / 2 )
+ screwHole.rotateX(-90)
.translateX( crossSection.x/2 + thickness )
.translateY( crossSection.y + thickness*2 )
.translateZ( up.top - cornerExtra/2 )
}.build()
return up and forward remove holes
}
@Piece
meth jointC() : Shape3d {
val forward = endStop( crossSection.y + thickness + cornerExtra ).rotateZ(90).toOrigin()
val right = endStop( crossSection.y + thickness + cornerExtra ).rotateZ(90).rotateY(90).toOrigin()
val holes = Compound().apply {
+ screwHole.rotateY(90)
.translateX( crossSection.y + thickness*2 )
.translateY( crossSection.x/2 + thickness )
.translateZ( forward.top - cornerExtra/2 )
+ screwHole
.translateX( right.right - cornerExtra/2 )
.translateY( right.back/2 )
.translateZ( right.top )
}.build().color("Red")
return (forward and right) remove holes
}
@Piece
meth cornerA() : Shape3d {
val one = jointA()
val forward = endStop( crossSection.y + thickness + cornerExtra )
.rotateZ(90).rotateX(-90).toOrigin()
val holes = Compound().apply {
+ screwHole.rotateY(90)
.translateX( forward.right )
.translateY( forward.back - cornerExtra / 2 )
.translateZ( forward.top / 2 )
}.build()
return one and forward remove holes
}
@Piece
meth cornerB() : Shape3d {
val one = jointA().rotateZ(90).mirrorX()
val right = endStop( crossSection.y + thickness + cornerExtra )
.rotateY(90).toOrigin().leftTo(one.left)
val hole = screwHole.rotateX(-90)
.translateX( right.right - cornerExtra / 2 )
.translateY( right.back )
.translateZ( right.top / 2 )
return one and right remove hole
}
meth angledCornerA(angle : double) : Shape3d {
val radians = angle / 180 * Math.PI
val flat = jointC().rotateX(-90).bottomTo(0)
val up = endStop( cornerExtra + crossSection.y * Math.sin(Math.abs(radians)) )
val hole = screwHole.rotateY(90)
.translateX( up.right )
.translateZ( up.top - cornerExtra / 2 )
.color("Red")
val deltaZ = if (angle < 0) { -crossSection.y * Math.sin(-radians) } else { 0.0 }
val angledUp = (up remove hole)
.translateZ( deltaZ )
.frontTo(-thickness).rotateX(-angle).translateY(thickness)
.leftTo(0)
.translateZ( flat.top - thickness*2 )
return flat and angledUp
}
meth angledCornerB(angle : double) : Shape3d {
val radians = angle / 180 * Math.PI
val flat = jointC().rotateX(-90).bottomTo(0)
val up = endStop( cornerExtra + crossSection.x * Math.sin(Math.abs(radians)) ).rotateZ(90)
val hole = screwHole.rotateY(90)
.translateX( up.right )
.translateZ( up.top - cornerExtra/2 )
.color("Red")
val deltaZ = if (angle < 0) { -crossSection.x * Math.sin(-radians) } else { 0.0 }
val angledUp = (up remove hole)
.translateZ( deltaZ )
.frontTo(0).rotateX(-angle)
.translateZ( flat.top - thickness*2 )
.leftTo(0)
return flat and angledUp
}
@Piece
meth cornerReinforcement() : Shape3d = cornerReinforcement( 90, thickness )
meth cornerReinforcement( size : double, panelThickness : double ) : Shape3d {
val clearance = 0.3
val forCorner = Square( thickness, thickness + cornerExtra )
.offset(clearance)
.rotate(-90).frontTo(-clearance).also(2)
val flatShape = Triangle( size+thickness, size+thickness ).intersection(Square(size)) -
forCorner
val flat = flatShape.toPolygon()
.smartExtrude( panelThickness )
.bottom( Chamfer(chamfer) )
val flap = Square( flatShape.back - forCorner.back, screwHole.size.x + 4 )
.roundCorner(1,chamfer)
.roundCorner(0,chamfer)
.smartExtrude(thickness)
.top( Chamfer( chamfer ) )
val screwHoles = screwHole
.translateZ(flap.top)
.centerYTo( flap.middle.y )
.centerXTo( 10 )
.centerXTo( flap.size.x - 10 ).also()
val flaps = (flap remove screwHoles)
.rotateX(-90).bottomTo(0)
.leftTo( forCorner.back )
.bottomTo( flat.top - chamfer )
return flat + flaps.rotateZ(90).mirrorX().also(2)
}
@Piece
meth spacer() : Shape3d {
val shape = Square( crossSection ).offset(-1 )
.center().roundAllCorners(3)
val hole = Circle( 3 )
return (shape - hole).smartExtrude( thickness )
.edges( Chamfer(0.6) )
}
@Piece
meth spacer(length : double) : Shape3d {
val shape = Square( length, crossSection.y ).offset(-1 )
.center().roundAllCorners(3)
val holes = Circle( 3 )
.leftTo( shape.left + 10 ).mirrorX().also()
return (shape - holes).smartExtrude( thickness )
.edges( Chamfer(0.6) )
}
meth angleA( angle : double ) : Shape3d {
val radians = angle / 180 * Math.PI
val l1 = (crossSection.x + thickness*2) / Math.cos( radians )
val l2 = (crossSection.x + thickness*2) * Math.tan( radians ) + angleExtra + thickness
val part1 = through( l1 ).rightTo( thickness )
val screwHole1 = screwHole
.rotateY(-90)
.leftTo(part1.left)
.translateZ( part1.top *0.66 )
val part2 = through( l2 )
val screwHole2 = screwHole.rotateY(90)
.translateX( crossSection.x/2 + thickness )
.translateZ( l2 - 3 - angleExtra/2 )
val angled = (part2 remove screwHole2)
.rightTo( thickness )
.rotateY( 90-angle )
.bottomTo(0)
return part1 and angled remove screwHole1
}
meth angleB( angle : double ) : Shape3d {
val radians = angle / 180 * Math.PI
val l1 = (crossSection.y + thickness*2) / Math.cos( radians )
val l2 = (crossSection.y + thickness*2) * Math.tan( radians ) + angleExtra + thickness
val part1 = through( l1 ).frontTo(-thickness)
val screwHole1 = screwHole
.rotateX(90)
.frontTo( part1.front )
.translateZ( part1.top * 0.66 )
val part2 = through( l2 )
val screwHole2 = screwHole.rotateX(-90)
.translateY( crossSection.y/2 + thickness )
.translateZ( l2 - 3 - angleExtra/2 )
val angled = (part2 remove screwHole2)
.backTo( thickness )
.rotateX( -90 + angle )
.translateY( crossSection.y )
.bottomTo(0)
return part1 and angled remove screwHole1
}
// TODO Buggy when crossSection.x much larger than crossSection.y.
// i.e. only works when the wood is roughly square.
meth angleC( angle : double ) : Shape3d {
val radians = angle / 180 * Math.PI
val l1 = (crossSection.x + thickness*2) / Math.cos( radians )
val l2 = (crossSection.y + thickness*2) * Math.tan( radians ) + angleExtra + thickness
val part1 = through( l1 ).rotateZ(90).rightTo(thickness).frontTo(0)
val screwHole1 = screwHole
.rotateY(-90)
.translateY(crossSection.y/2 + thickness)
.leftTo( part1.left )
.translateZ( part1.top * 0.66 )
val part2 = through( l2 )
val screwHole2 = screwHole.rotateY(90)
.translateX( part2.right )
.translateZ( l2 * 0.5 )
val angled = (part2 remove screwHole2)
.rightTo( thickness )
.rotateY( 90 - angle )
.bottomTo(0)
.frontTo(0)
return part1 and angled remove screwHole1
}
@Piece
meth angleA45() = angleA( 45 )
@Piece
meth angleB45() = angleB( 45 )
@Piece
meth angleC45() = angleC( 45 )
@Piece
meth straightConnectorA() = angleA(0)
@Piece
meth straightConnectorB() = angleB(0)
override fun build() : Shape3d {
val height = 350
val width = 300
val length = 500
val sides = Cube( crossSection.y, length, crossSection.x )
.leftTo( -width/2 ).centerY()
.mirrorX().also()
.topTo( height ).also()
.color("Green")
val frontAcross = Cube( width - crossSection.y*2, crossSection.y, crossSection.x )
.centerX().frontTo( -length/2 )
.mirrorY().also()
.topTo( height ).also()
.color("LightGreen")
val uprights = Cube( crossSection.y, crossSection.x, height - crossSection.x * 2 )
.bottomTo( crossSection.x )
.leftTo( sides.left )
.frontTo( sides.front )
.mirrorX().also().mirrorY().also()
.color("DarkGreen")
val corners = cornerB()
.leftTo( -width/2 - thickness ).frontTo(-length/2-thickness).bottomTo(-thickness)
.mirrorX().also().mirrorY().also().mirrorZ().translateZ(height).also(2)
val cornerReinforcement = cornerReinforcement()
.rotateY(-90)
.rightTo( uprights.right )
.frontTo( uprights.front + crossSection.x )
.bottomTo( crossSection.x )
return Compound().apply {
+ sides
+ frontAcross
+ uprights
+ corners
+ cornerReinforcement
}.build()
}
}