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") } }