/Boxes/MiniBeerCrate.foocad

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.
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 : AbstractModel() {
@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")
}
}

