/Boxes/ComponentOrganiser.foocad

A storage solution for small parts, such as nuts and bolts, electrical components etc.
This is compatible with Raaco's "A Series" as well as other generic brands.
- [https://www.raacostorage.co.uk/PBSCCatalog.asp?CatID=2279874]
- [https://www.raacostorage.co.uk/a-range-inserts-c102x2279884]
NOTE. The price of Raaco HandyBox 55 is less than the equivalent weight of filament. So do yourself a favour, buy rather than print (unless you need something very specific)!
FYI. Racco refers to this as the "55 Series" as well as the "A range". 55 is the width (x-axis) of the smallest insert. However, if you change the custom values, you can recreate their other series too.
The Raaco cases are arranged in the opposite orientation to my model (oops). So in their products "55" is the depth (y-axis) of the smallest insert.
Inserts are any integer multiples of the basic unit. i.e. the 2x1 insert is twice as wide as the 1x1 (smallest) insert (ignoring clearances/spacing).
The inserts are forced to line up correctly with the aid of feet on the inserts, and a pattern in the drawer/case.
The case's lid can also have a pattern which provides two roles :
- Ensures the inserts cannot move
- Ensures tiny component cannot move from one insert into another even when the lid is perfectly fitting.
Comparison with GridFinity
- Uses a SQUARE layout, rather than rectangular. i.e. a 1x2 is the same size as a 2x1 (but rotated 90°). This is my buggest gripe. Such a simple change would make a big difference.
- Blocks can be various heights (mostly conforming to multiples of a standard height??)
- Blocks can be stacked.
- No need to glue on feet.
- Blocks align to the base much more securely, reliably and easily.
- Wasteful of plastic (and print time) due to the need for a thick base (instead of small feet).
- A vast collection of custom blocks.
Despite GridFinity's advantages, I still use this system.
- It fits in well with my existing Raaco (and generic knock-off) cases.
- I have no need for GridFinity's extra features (such as stacking).
- I only need simple inserts.
- I like the flexibility of the rectangular layout. IMHO, Square is just WRONG :-( YMMV.
- I can buy rugged cases/boxes and mix and match inserts as needed.
- Even if I could print such rugged cases/boxes, the printing them would be more expensive.
import uk.co.nickthecoder.foocad.extras.v1.*
import static uk.co.nickthecoder.foocad.extras.v1.Extras.*
import static uk.co.nickthecoder.foocad.arrange.v1.Arrange.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.*
class ComponentOrganiser : AbstractModel() {
@Custom( about="The gap between the inserts" )
var insertGap = 0.5
// The size of the inserts. Note "insertGap" is subtracted from the
// x and y sizes of the actual inserts.
// Note Raaco states that the smallest A insert is 39x55x47
// Swap width and depth for inserts oriented the other way.
@Custom
var unitWidth = 55
@Custom
var unitDepth = 40 // NOTE this isn't 39!
@Custom
var unitHeight = 46.5 // Does NOT include the feet.
@Custom
var shoeHeight = 1.8
@Custom( about="Use by the `sixth`" )
var tabHeight = 6.0
var insertThickness = 1.0
var insertTaper = 1.0
var insertRadius = 2.0
// The radius of the circles in the drawers that the feet fit into.
var shoeRadius = 9
meth floorShape( sizeX : int, sizeY : int ) : Shape2d {
val freeWidth = unitWidth * sizeX
val freeDepth = unitDepth * sizeY
val thickness = 1.8
val circles = Circle( shoeRadius ).ring( thickness )
.repeatX( sizeX+1, unitWidth )
.repeatY( sizeY+1, unitDepth )
.center()
val clippedCircles = circles / Square( freeWidth, freeDepth ).center()
// Lines connecting the shoes.
// These aren't *needed*, but they do add extra strength to the floor,
// for little extra time and plastic. They look nice too ;-)
val verticals = Square( thickness, unitDepth - shoeRadius*2 )
.repeatY( sizeY, unitDepth )
.repeatX( sizeX+1, unitWidth )
.center()
val horizontals = Square( unitWidth - shoeRadius*2, thickness )
.repeatX( sizeX, unitWidth )
.repeatY( sizeY+1, unitDepth )
.center()
return clippedCircles + verticals + horizontals
}
meth floorPattern( sizeX : int, sizeY : int ) = floorShape( sizeX, sizeY ).extrude( shoeHeight )
meth lidShape( sizeX : int, sizeY : int ) : Shape2d {
val outside = insertShape(1,1).offset( insertTaper - 1 )
val inside = outside.offset(-1.2)
val one = (outside - inside)
return one
.repeatY( sizeY, unitDepth )
.repeatX( sizeX, unitWidth )
.center()
}
meth lidPattern( sizeX : int, sizeY : int ) : Shape3d {
return lidShape( sizeX, sizeY ).extrude(2)
}
meth footShape() : Shape2d {
val width = unitWidth - insertGap
var depth = unitDepth - insertGap
val profile = Square(
width - insertTaper*2 - insertThickness * 2,
depth - insertTaper*2 - insertThickness * 2
).center().roundAllCorners(insertRadius,6)
return profile.offset( insertThickness ) /
Circle( shoeRadius-1 ).translate( 1/2*unitWidth, 1/2*unitDepth )
}
meth insertShape( x : int, y : int ) : Shape2d {
val width = unitWidth * x - insertGap
var depth = unitDepth * y - insertGap
return Square(
width - insertTaper*2 - insertThickness * 2,
depth - insertTaper*2 - insertThickness * 2
).center().roundAllCorners(insertRadius,6)
}
meth insert( x : int, y : int ) : Shape3d {
val height = unitHeight - insertGap
val profile = insertShape( x, y )
// We build them upside down, starting at the inside of the base.
val main = ExtrusionBuilder().apply {
forward( height - insertThickness )
crossSection( profile )
forward( -height + insertThickness )
crossSection( insertTaper )
crossSection( insertThickness )
forward( height )
crossSection( -insertTaper )
}.build().mirrorZ().toOriginZ()
return main
}
@Piece
meth foot() = footShape().extrude(shoeHeight-0.2)
@Piece
meth insert1x1() = insert( 1, 1 )
@Piece
meth insert1x2() = insert( 1, 2 )
@Piece
meth insert1x3() = insert( 1, 3 )
@Piece
meth insert1x4() = insert( 1, 4 )
@Piece
meth insert2x1() = insert( 2, 1 )
@Piece
meth insert2x2() = insert( 2, 2 )
@Piece
meth insert2x3() = insert( 2, 3 )
@Piece
meth insert2x4() = insert( 2, 4 )
@Piece
meth insert3x1() = insert( 3, 1 )
@Piece
meth insert3x2() = insert( 3, 2 )
@Piece
meth insert3x3() = insert( 3, 3 )
@Piece
meth insert3x4() = insert( 3, 4 )
@Piece
meth insert4x1() = insert( 4, 1 )
@Piece
meth insert4x2() = insert( 4, 2 )
@Piece
meth insert4x3() = insert( 4, 3 )
@Piece
meth insert4x4() = insert( 4, 4 )
/**
Designed to fit 5 smaller containers inside a 1x2 insert, specifically for
through-hole resistors.
*/
@Piece
meth tabbedFifths() = arrangeY( 0.7,
tabbedFifth( 0 ),
tabbedFifth( 1 ),
tabbedFifth( 2 ),
tabbedFifth( 3 ),
tabbedFifth( 4 )
).centerY() + insert1x2().rotateZ(90)
.centerY()
.bottomTo( -insertThickness )
.previewOnly()
@Piece( about="Mini tubs, arranged as a 1x5 inside `insert2x2`" )
fun tabbedFifth( tabPosition : int ) : Shape3d {
val width = unitDepth * 2 - insertThickness*2 - insertTaper * 2 - 1
val depth = (unitWidth - insertThickness*2 - insertTaper * 2) / 5 - 0.5
val height = unitHeight - insertGap - insertThickness - tabHeight - 1
val thickness = insertThickness
val profile = Square( width, depth ).center().roundAllCorners(2,4)
val main = ExtrusionBuilder().apply {
crossSection( profile )
forward( height )
crossSection()
crossSection( - thickness )
forward( -height + thickness )
crossSection()
}.build()
val tabWidth = (main.size.x - 4) / 5
val tab = Square( tabWidth, tabHeight )
.roundCorner(3,3)
.roundCorner(2,3)
.extrude( thickness ).rotateX(90)
.bottomTo( main.top )
.backTo( main.back )
.leftTo(main.left + 2 + tabPosition * tabWidth )
return main + tab
}
@Piece( about="Mini tubs, arranged as a 3x2 inside `insert2x2`" )
fun tabbedSixth() : Shape3d {
val width = (unitDepth * 2 - insertThickness*2 - insertTaper * 2) / 3 - 1.0
val depth = (unitWidth - insertThickness*2 - insertTaper * 2) / 2 - 1.0
val height = unitHeight - insertGap - insertThickness - tabHeight - 1
val thickness = insertThickness
val profile = Square( width, depth ).center().roundAllCorners(2,4)
val main = ExtrusionBuilder().apply {
crossSection( profile )
forward( height )
crossSection()
crossSection( - thickness )
forward( -height + thickness )
crossSection()
}.build()
val tabWidth = (main.size.x - 4)
val tab = Square( tabWidth, tabHeight )
.roundCorner(3,3)
.roundCorner(2,3)
.extrude( thickness ).rotateX(90)
.bottomTo( main.top )
.backTo( main.back )
.leftTo(main.left + 2 )
val one = main + tab
val set = one.tileX(3, 1).tileY(2, 1).centerXY()
return set + insert1x2().rotateZ(90)
.centerY()
.bottomTo( -insertThickness )
.previewOnly()
}
@Piece( printable=false )
override fun build() : Shape3d {
val i1x1 = insert1x1().color("Yellow")
val i1x2 = insert1x2().color("Yellow").darker()
val i1x3 = insert1x3().color("Yellow").darker().darker()
val i2x1 = insert2x1().color("Orange")
val i2x2 = insert2x2().color("Orange").darker()
val i2x3 = insert2x3().color("Orange").darker().darker()
val i3x1 = insert3x1().color("Red")
val i3x2 = insert3x2().color("Red").darker()
val i3x3 = insert3x3().color("Red").darker().darker()
// NOTE, the feet are only included on one of the inserts.
val feet = foot().mirrorX().also().mirrorY().also()
.centerXTo( i1x1.middle.x ).centerYTo( i1x1.middle.y )
.topTo( i1x1.bottom )
.color("Blue")
val floorPattern = floorPattern( 6, 6 )
.centerXY()
.topTo( 0 )
.color("Purple")
val inserts = arrangeX(
arrangeY(insertGap, i1x1 + feet, i1x2, i1x3),
arrangeY(insertGap, i2x1, i2x2, i2x3),
arrangeY(insertGap, i3x1, i3x2, i3x3)
).centerXY()
val lidPattern = lidPattern( 6, 6 )
.centerXY()
.topTo( inserts.top )
.previewOnly()
return inserts + floorPattern + lidPattern
}
}

