Exit Full View
Up

/GardenFurniture/Hardware/BoxConstruction.foocad

BoxConstruction

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

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

    }

}