Exit Full View
Up

/Boxes/MiniBeerCrate.foocad

MiniBeerCrate

Mini beer crates to hold batteries.

Inspired by https://www.printables.com/model/173788-customizable-stackable-beer-crate-for-all-types-of

Crates for AA, AAA and 9V are all the same height. The crate for 18650s is taller. All are the same size in X & Y, and therefore stack on top of each other.

Glue the "stackable" piece to the base, which makes a stack of crates nice and snug.

My crates are a little bit taller than the originals. Mine also have writing on the sides.

Print Notes

My first prints used 0.2 layer height with 0.4mm nozzle, but I them printed a couple with 0.3mm layers, and they came out fine.

Choose different colours for each type of battery. Use black "stackable" pieces for all of them.

FooCAD Source Code
import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*

class Crate : Model {

    @Custom
    var size = Vector3(62,47,51)

    @Custom
    var midHeight = size.z/2

    @Custom
    var radius = 2.5

    @Custom
    var wallT = 1.2

    @Custom
    var baseT = 1.0

    @Custom
    var ribSize = Vector2(1.6, 3.2)

    @Custom
    var ribDist = 6.0

    @Custom
    var dividerT = 1.0

    var windowRadius = 6.0

    fun dividersX( x : int ) : Shape3d {
        return Cube( size.x, dividerT, midHeight )
            .spreadY( x, size.y, 1 ).centerXY()
    }

    fun dividersY( y : int ) : Shape3d {
        return Cube( dividerT, size.y, midHeight )
            .spreadX( y, size.x, 1 ).centerXY()

    }

    // Glue to the bottom of the crates so that they stack nicely
    @Piece
    fun stackable() : Shape3d {
        val slack = size.x * 0.005 + 0.3

        val gap = ribSize.y * 2 + slack * 2
        println( "Slack = $slack gap = $gap" )


        val quarter = Square( (size.x - gap -slack) * 0.5 , (size.y - gap - slack) * 0.5 )
            .rightTo( size.x/2 - slack )
            .backTo( size.y/2 - slack )
            .roundAllCorners( radius + slack )
        
        val raised = (quarter - quarter.offset( -ribSize.x ) ).extrude( baseT*2 )
            .mirrorX().also()
            .mirrorY().also()

        val profile = insideProfile().offset( ribSize.y )
        val main = profile - profile.offset( -gap/2-ribSize.x )

        val middle = (Square( gap + ribSize.x*2 ).center() - Square( gap ).center() )

        return raised + main.extrude(baseT) + middle.extrude(baseT)
    }

    @Piece
    fun plain() : Shape3d {
        val crate : Shape3d = crate()

        val pattern = Square( size.x, ribSize.x ).spreadY(2, size.y - ribSize.x*2, 1).center() +
            Square( ribSize.x, size.y ).spreadX(3, size.x - ribSize.x*1, 1).center()

        return crate + base(4,3) + base(pattern)
    }
   
    @Piece
    fun AA() : Shape3d {
        val crate : Shape3d = crate().color("Red")
        val batteries = Cylinder(50.5, 14.5 /2 ).previewOnly().bottomTo( baseT )
            .spreadX(4,size.x).spreadY(3,size.y).centerXY()


        return crate + text("AA") + base(4,3) + dividersX(2) + dividersY(3) + batteries
    }

    @Piece
    fun AAA() : Shape3d {
        val crate : Shape3d = crate().color("LightSkyBlue")
        val batteries = Cylinder(44.5, 10.5 /2 ).previewOnly().bottomTo( baseT )
            .spreadX(5,size.x).spreadY(4,size.y).centerXY()
        
        return crate + text( "AAA" ) + base(5, 4) + dividersX(3) + dividersY(4) + batteries
    }

    /**
        For cells in cardboard, with plastic covering.
    */
    @Piece
    fun button() : Shape3d {
        val crate : Shape3d = crate()
        
        return crate + text( "Button" ) + base(5, 4) + dividersY(3)
    }

    @Piece
    fun nineV() : Shape3d {
        val crate : Shape3d = crate().color("Yellow")

        val battery = Square(17.5,26.5).roundAllCorners(2).extrude(48.5).previewOnly()
        val batteries = battery.spreadX(3, size.x).centerX().frontTo(-size.y/2) +
            battery.rotateZ(90).spreadX(2, size.x).centerX().backTo( size.y/2)
        val dividerA = Cube( size.x, dividerT, size.z*0.5).centerX().centerYTo(size.y*0.1)
        val dividerB = Cube( dividerT, size.y - (size.y/2 - dividerA.back), midHeight )
            .backTo( dividerA.front )
            .spreadX( 2, size.x, 1 ).centerX()
        val dividerC = Cube( dividerT, size.y/2 - dividerA.back, midHeight )
            .frontTo( dividerA.back ).centerX()

        return crate + text("9V") + base(4, 3) + batteries.bottomTo( baseT ) + dividerA + dividerB + dividerC
    }
    
    @Piece
    fun round18650(): Shape3d {
        size = Vector3(size.x, size.y, size.z + 16 ) // This was 14mm higher for my first print :-(
        midHeight += 10
        val crate : Shape3d = crate().color("Green")
        val battery = Cylinder(65, 18/2).bottomTo(baseT).previewOnly()
        val batteries = battery.spreadY(2, size.y).leftTo(-size.x/2).centerY().tileX(2,12) +
            battery.tileX(2,12).rightTo(size.x/2)

        val dividerA = Square( size.x + size.y, dividerT ).tileY( 4, 20 ).rotate(45)
            .center().rotate(90).also().translateX(-7) /
            insideProfile()
            
        val dividers = dividerA.extrude( midHeight )
 
        val pattern = Square( 2, size.y ).spreadX(4, size.x, 0.5).center() +
            Square( size.x, 2 ).spreadY( 3, size.y, 0.5 ).center()
        val base : Shape3d = base(pattern)

        return crate + text("18650") + dividers + base + batteries
    }

    @Piece
    fun vape() : Shape3d {
        val crate : Shape3d = crate().color("LightSkyBlue")
        val bottles = Cylinder(74, 10 ).previewOnly().bottomTo( baseT )
            .spreadX(3,size.x).spreadY(2,size.y).centerXY()
        
        return crate + text( "VAPE" ) + base(3, 2) + dividersX(1) + dividersY(2) + bottles
 

    }


    @Piece
    fun usb() : Shape3d {
        val crate : Shape3d = crate()

        val across = 6

        val usb = (Cube( 4,12, 12 ).centerXY() +
            Cube( 8, 18, 36 ).translateZ(12).centerXY()
        ).previewOnly()
        val usbs = usb.spreadY(2, size.y, 0.5 )
            .spreadX( across, size.x, 0.5 )
            .centerXY()

        val hole = Square( 4.7, 13 )
        val slot = (hole.offset(dividerT) - hole).chamferedExtrude( baseT + 12, 0, 0.4 )   

        val slots = slot.spreadY(2, size.y, 0.5 )
            .spreadX( across, size.x, 0.5 )
            .centerXY()

        val dividerA = Cube( dividerT, size.y, 10 ).rightTo(slot.size.x).also()
            .spreadX( across, size.x, 0.5 )
            .centerXY()

        val dividerB = Cube( size.x, dividerT, 10 ).centerXY()

        return crate + text("USB") + base(2, 2) + slots +  dividerA + dividerB + usbs

    }

    @Piece
    fun sdCard() : Shape3d {
        val crate : Shape3d = crate()
        val sdCard = Cube( 24, 2, 32).previewOnly().centerXY().bottomTo( baseT )
            .spreadY(7, size.y, 0.5)
            .spreadX(2, size.x, 0.5)
            .centerXY()

        return crate + text("SD") + base(4, 4) + dividersX(6) + dividersY(1) + sdCard

    }
    

    /**
        These batteries fit nicely into the 9V crate layout, with three batteries in each section.
        So I'll print a crate without labels on the sides, so I can use it for 9V and 602540s.

        However, I may well store these batteries along side my 503450's (see below).
    */
    @Piece
    fun flat602540(): Shape3d {
        val crate : Shape3d = crate().color("Yellow")

        val battery = Cube( 6, 26, 41 ).previewOnly()
        val batteries = battery.tileX(3, 0.5)
            .leftTo(-size.x/2).frontTo(-size.y/2)

        val dividerA = Cube( size.x, dividerT, size.z*0.5).centerX().centerYTo(size.y*0.1)
        val dividerB = Cube( dividerT, size.y - (size.y/2 - dividerA.back), midHeight )
            .backTo( dividerA.front )
            .spreadX( 2, size.x, 1 ).centerX()
        val dividerC = Cube( dividerT, size.y/2 - dividerA.back, midHeight )
            .frontTo( dividerA.back ).centerX()

        return crate + base(4, 3) + batteries.bottomTo( baseT ) + dividerA + dividerB + dividerC
    }

    /**
        This can also hold 6mm thick batteries.
    */
    @Piece
    fun flat503450(): Shape3d {
        val crate : Shape3d = crate().color("Green")

        val battery = Cube( 5, 34, 50 ).previewOnly()
        val batteries = battery.spreadX(8, size.x, 0.2)
            .centerXY()

        return crate + base(4, 3) + text("Flat")+ batteries.bottomTo( baseT ) + dividersY(7)
    }

    fun insideProfile() = Square( size.x, size.y ).roundAllCorners( radius ).center()

    fun text( str : String ) : Shape3d {
        val text = Text(str, BOLD).center().chamferedExtrude(0.4, 0, 0.2).rotateX(90)
            .centerZTo( midHeight/2)
        val sides = text
            .backTo( -size.y/2 - wallT )
            .rotateZ(180).also()
        val ends = if ( text.size.x < size.y - ribDist*2 ) {
            text
                .rotateZ(90)
                .leftTo( size.x/2 +wallT )
                .rotateZ(180).also()
        } else {
            Cube(0)        
        }
        return sides + ends
    }


    fun base( x : int, y : int ) : Shape3d {

        val dx = size.x / x
        val dy = size.y / y
        val crosses = PolygonBuilder().apply {
            moveTo(0,0)
            lineBy( dx, dy )
        }.buildPath().thickness( ribSize.x )
            .repeatX( x, dx ).repeatY( y, dy ).center()
            .mirrorY().also()

        return base( crosses )
    }

    fun base( pattern : Shape2d ) : Shape3d {
        return ( pattern / Square( size.x, size.y ).roundAllCorners( radius ).center() )
            .extrude( baseT )
    }

    fun window( width : double, length : double ) : Shape3d {
        val profile = Square( width, size.z - midHeight - ribDist/2 - ribSize.x-ribSize.y).center()
            .roundCorner(3, windowRadius)
            .roundCorner(2, windowRadius)

        val window = profile.offset(ribSize.x/2).extrude( length ) remove
            profile.extrude( length + 2 ).bottomTo(-1)

        return window.rotateX(90).centerY()
            .bottomTo( midHeight + ribSize.x/2 )
    }    

    fun crate() = crate( 2, 1 )

    fun crate( windowsX : int, windowsY : int ) : Shape3d {
    
        val profile = Square( size.x, size.y ).roundAllCorners( radius ).center()

        val chamfer = ribSize.y - wallT
        val solid = ExtrusionBuilder().apply {
            crossSection( profile.offset( ribSize.y ) )
            forward( ribSize.x )
            crossSection()
            crossSection( wallT -ribSize.y )
            forward( midHeight - chamfer - ribSize.x )
            crossSection()

            forward( chamfer )
            crossSection( chamfer )
            forward( ribSize.x )
            crossSection()
            crossSection( wallT-ribSize.y )

            forward( size.z + baseT - midHeight - chamfer*2 - ribSize.x*2 - ribDist/2 )
            crossSection()

            forward( chamfer )
            crossSection( chamfer )
            forward( ribSize.x )
            crossSection()
            crossSection( wallT-ribSize.y)

            forward( ribDist/2 )
            crossSection()

            forward( chamfer )
            crossSection( chamfer )
            forward( ribSize.x )
            crossSection()
            crossSection( wallT-ribSize.y)

        }.build()

        val vertical = Cube( ribSize.y, ribSize.x, size.z + baseT ).centerY()

        val cornerRib = vertical.leftTo(radius).rotateZ(45)
            .translate(size.x/2 - radius, size.y/2 - radius,0)
        val ribA = vertical.translate( size.x/2, size.y/2 - ribDist, 0 )
        val ribB = vertical.rotateZ(90).translate( size.x/2- ribDist, size.y/2 , 0 )
        val corner = cornerRib + ribA + ribB
        val corners = corner.mirrorX().also().mirrorY().also()

        // Negative chamfer at the top, for a rounded internal.
        val middle = profile.chamferedExtrude( solid.top + 2, 0, -1-wallT ).bottomTo(-1)

        val divider1 = Cube( ribSize.x, ribSize.y, solid.top - midHeight )
            .translateX( ribDist ).also().centerX()
            .topTo( solid.top )
            .backTo( -size.y/2 )

        val divider2 = Cube(  ribSize.y, ribSize.x, solid.top - midHeight )
            .translateY( ribDist ).also().centerY()
            .topTo( solid.top )
            .leftTo( size.x/2 )

        val window1Width = (corner.left * 2 - divider1.size.x * (windowsX-1)) / windowsX
        val window1 = window( window1Width, size.y + ribSize.y*2 )
        val windows1 = if (windowsX > 1 ) {
            divider1.spreadX( windowsX-1, corner.left * 2 + ribSize.x, 1).centerX() and
            window1
                .spreadX( windowsX, corner.left * 2 + ribSize.x).withCavities()
                .centerX()
        } else {
            window1
        }


        val window2Width = (corner.front * 2 - divider2.size.y * (windowsY-1)) / windowsY
        val window2 = window( window2Width, size.x + ribSize.y*2 )
            .rotateZ(90)
        val windows2 = if (windowsY > 1 ) {
            divider2.centerY() and
            window2
                .spreadY( windowsY, corner.front*2 + ribSize.x).withCavities()
                .centerY()
        } else {
            window2
        }
            
        return (solid and windows1 and windows2) - middle + corners
    }

    override fun build() : Shape3d {

        return AA() + stackable().mirrorZ().color("Black")
    }

}