/* A torch with a wide COD LED strip which can lay flat on flat surface The light can be directed up/down by simply twisting the body. Powered by a 18650 battery, through a boost circuit to raise to 12V needed for the LED cob. Includes a USB battery charge port at the other end to the on/off switch. The feet can be taken on/off if required. The feet include impressions for magnets to glued into. The battery is held in place using a friction fit using tape to pad it out slightly. MI used masking tape because it has high friction. Ensure that you cover the terminals well. It could slide about! I also taped the end of the charger module to be extra safe, and the boost module is 100% protected by the "boostHolder" pieces. Build parts ----------- Build in several parts. Use the custom "piece" field : foot (x2) tube chargerEnd switchEnd boost (x2) (to secure the boost circuit within the tube) Required M3 bolts Bought parts ------------ A 18650 battery. I use 2nd hand ones from old laptops. Usually only one or two cells have died when the battery as a whole is dead. I don't tend to use torches for long periods, so a high capacity battery is a waste! Search ebay (or other shop) for : usb battery charge module dc boost module mounting holes (3.x V to 12V no usb socket FYI, The boost modules with mouting holes are becoming rarer. If you can't find any, the "boost" piece won't be of use. Requirements ------------ At least 17cm build height! Possible Improvements --------------------- Add posts to the "buttonEnd", so that it can never rotate (twisting the wires) Cut away the tube to allow space for the wires from the LED cob strip. */ import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.* import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.* class CobTwist : Model { // Size of the LED module @Custom var length = 173 val width = 15.5 var thickness = 2.7 val buttonD = 13 // Old one was 16.5 val endT=8 val endBase=2 val endChamfer=1 val endExtra = 3 val endSkin=0.8 // Thickness of the tube walls. On a small extrusion this seems a little // flimsy, but the final result feels very rigid to me. val tubeT = 0.6 val radius=13 // external radius of the tube // The boost module is 22 mm, so we need an internal radius of at least 11 mm fun internalRadius() = radius - tubeT val footT = 5 // Diff between internal and external radius of the foot. val magnetD = 11 // Diameter of the magnets on the feet (include some slack?) val magnetT = 1.0 // Thickness of the magnets (include some slack?) // For an interference fit between the tube and the ends. // I recommend gluing the ends on. You don't want anybody // shorting out the battery (lithium batteries are dangerous!) val slack = 0.3 // I didn't include any slack at first. (I ussumed that the split nature // of the foot would suffice. However, the feet tend to turn the ends, // so a higher value may be better (0.4 maybe??? Testing for the first time...) val footSlack = 0.4 //@Custom var text = "" @Piece fun others() : Shape3d { return ( buttonEnd() + chargerEnd().translateX(30) + boostHolder().translateY(-3).rotateZ(180).also().translateX(60) + foot().tileX(2,5).translateY(-35) ).center() } // ==== FUNCTIONS BEGIN ==== override fun build() : Shape3d { return buttonEnd() + chargerEnd().rotateX(180).translateZ(length+30) + tube().translateZ( endBase ) + led().translate( 0,10,endBase ) + battery().translateZ(30).translateY(-2) + foot() + foot().mirrorZ().translateZ(length) } // For debugging only. fun battery() = Cylinder( 65, 9 ).color("GhostWhite") // Used by the "chargerEnd" and "buttonEnd" pieces. // Goes on both ends of the tube. Each end is modified in different ways. fun end() : Shape3d { val outside = Circle( radius + slack + endSkin ) val inside = Circle( radius + slack ) return ExtrusionBuilder().apply { crossSection(Circle( radius + slack - endChamfer)) forward(endChamfer) crossSection(outside) forward(endT-endChamfer) crossSection(outside) crossSection(inside) forward(-endT + endBase ) crossSection(inside) }.build().color( "LightGreen" ) } // The "chargerEnd" piece. Includes a hole and mounting post // for a usb charger module. Ensure you get one with no component on // one side, as you will be gluing it to a plastic post. @Piece fun chargerEnd() : Shape3d { val hole = Cube( 8.5, 3.5, endT*3 ).center() val floor = Cube( 18, 6, 10 ).centerXY().translateZ(0.9) val entry = Cube( 11, 5, 0.5).centerXY().translateZ(-0.1) // If you glue the charger module to the post with epoxy, // then these holes will give extra support. // If using super-glue, then don't include these holes, as they // will only reduce the surface area, and give no mechanical advantage. val holes = Cube(2,10,2) .tileX(2,5) .tileZ(2,5) .centerXY() .translateZ(3) val post = Cube( 20, 0.6, 13 ).centerX().translateY(-3.5) + Cube( 0.6, 5, 13 ).translateY(-8) - holes val indentText = Text( text, BOLD, 7 ) .center() .mirrorX() .translateY(6.5) .extrude(0.6).translateZ( -0.01 ) .color("Orange") return end() - hole - floor - entry - indentText + post } // The "buttonEnd" piece. Includes a hole for a button. // Note, my button's retaining ring fouled again the cob LED strip, // so I glued it in place. // Consider using a smaller diameter button! // NOTE. I should have added post(s) to prevent the end from rotating. // (to prevent wires getting twisted). I'll have to glue mine in place. fun buttonEnd() : Shape3d { val post = Cube(0.6, 8, 8).centerXY().translateX(10) return end() - Cylinder( 30, buttonD / 2).center() + post.mirrorX().also() } // Used for debugging only. Shows the size of the boost module, as well // as the mounting holes. fun boostModule() : Shape3d { val board = Cube( 43.5, 21.5, 1.5 ).color("Green") val components = Cube(43.5, 21.5, 10).translateZ(1.5).color("WhiteSmoke") val holes = Cylinder.hole( 20, 1.5 ).translate(15,-7.5,-1) .translate(-30,15,-1).also() return (board + components).centerXY() - holes } @Piece fun boosts() = boostHolder().translateY(-3).rotateZ(180).also() // The "boost" piece. Print two of them to secure and protect the boost // module. Requires two short M3 bolts (no nuts). @Piece fun boostHolder() : Shape3d { val thick = 0.6 val height = 9 val r = internalRadius() - slack val oversize = Cube( r * 3, r * 3, height ).centerXY() val snug = Circle( r ) - Square( radius * 2 ).centerX().translateY(2) val inside = Circle( r-1 ) val block = Cube( 6,4,10 ).translate(-10,-2,0) val hole = Cylinder.hole(30,1.5).center().rotateZ(-90).rotateX(90).translate(-7.5,0,7.5) // val debug = boostModule().rotateZ(90).rotateX(90).translate(0,4,21.75+thick) // We make a "fake" chamfer at the bottom to prevent squishage of the // first layer making it too big to fit in the tube. val solid = (oversize - inside.extrude(height).translateZ(thick)) / (snug.extrude(height).translateZ(0.4) + snug.offset(-0.4).extrude(height)) return block - hole + solid } // The "tube" piece. // I had no trouble printing it, despite being tall and narrow, it didn't // get knocked over during the print (and I did not use a brim). // Consider printing a small version (using @Custum length), and // check that your LED strip fit within. @Piece fun tube() : Shape3d { val up = 7.5 val boardT = 2 val apeture = 11 val profile = ( Square( radius*2+1 ).center() - // Overly large square Circle( radius - tubeT ) + // Internal circle Square( radius*2, 20 ).centerX().translateY( up-tubeT ) - // Fill in the top Square( width, boardT ).centerX().translateY( up ) - // Slot for LED Square( apeture, 10 ).centerX().translateY( up ) // Apeture for light ) / Circle( radius ).convexity(3) val removeForSolder = Cube( width, 10, 5 ).centerX() .hull( Cube( 2, 10, 6 ).centerX().translateZ(8) ) .translateY(up) val removeForSolders = removeForSolder + removeForSolder.mirrorZ().translateZ(length) val removeForButton = Cylinder(21,10).translateZ(length-20) return profile.extrude( length ) - removeForSolders - removeForButton } // The "foot" piece. Print two identical copies. // These have a split design, so that they can be taken on/off easily // after the body of the torch is built. // I say "easily" - it takes a bit of brute force and coordination, // but there's no unsoldering involved (unlike the first design!) @Piece fun foot() : Shape3d { val internalR = radius + slack + endSkin + footSlack val externalR = internalR + footT val extra = 8 val foo = 47 // The width at floor level val thickness = 9 // Thickness at the top val thickness2 = 17 // Thickness at the bottom (Badly named!) val base = Square( foo, 10 ).roundAllCorners(3).centerX().translateY(-internalR-extra) val shape = (Circle( externalR ).hull(base)) // - Circle( internalR ) val profile = PolygonBuilder().apply { moveTo(-externalR,0) lineTo(externalR+extra,0) lineTo(externalR+extra,thickness2) bezierTo( Vector2(externalR+extra-20,thickness2), Vector2(10,thickness), Vector2(0,thickness) ) lineTo(-externalR,thickness) }.build() val center = ExtrusionBuilder().apply { forward(-0.1) crossSection( Circle( internalR - 1 ) ) forward(1) crossSection( Circle( internalR ) ) forward( thickness-2 ) crossSection( Circle( internalR ) ) forward(1) crossSection( Circle( internalR - 1 ) ) forward(30) crossSection( Circle( internalR - 1 ) ) }.build().color("Red") val inter = profile.extrude( foo ).centerZ().rotateX(90).rotateZ(-90) val split = ( Square(1,extra+2).centerX() + Square(8).center().rotate(45).translateY(extra+3.5) ) .extrude( thickness2+2 ) .centerX() .translate(0,-internalR-extra-1,-1) val magnetHoles = Cylinder.hole( magnetT+0.1, magnetD/2 ) .rotateX(90) .translateZ(thickness2/2) .translateY(-internalR-extra+magnetT) .translateX(10).mirrorX().also() return shape.extrude( thickness2 ).color( "Violet" ) / inter - center - magnetHoles - split } fun led() = Cube( width,thickness,length ) .centerX().mirrorY() .color("WhiteSmoke") fun line( fromX: double, fromY : double, toX : double, toY : double ) = line( Vector2(fromX, fromY), Vector2(toX, toY) ) fun line( from : Vector2, to : Vector2 ) : Shape2d { val across = (to-from).normal().unit()*2 return PolygonBuilder().apply { moveTo(from) lineTo(from+across) lineTo(to+across) lineTo(to) }.build() } fun box( name : String ) : Shape2d{ return Square( 60, 40 ).center().color("DarkGreen") + Text( name ).center().color("MintCream") } // A simple circuit diagram fun circuit() : Shape2d { val switch = box("Switch").translateX(-270) val led = box("LED").translateX(-180) val boost = box("Boost").translateX(-90) val battery = box("Battery") val charger = box("Charger").translateX(90) val plusToSwitch = line( -30,0, -60, -30 ) + line( -60,-30, -210,-30 ) + line( -210, -30, -240, -5 ) val switchToCharger = line( -240,5, -210, 30 ) + line( -210,30, 30,30 ) + line( 30,30, 60,5 ) val chargerToBoost = line( 60, 20, 25,50 ) + line( 25, 50, -25, 50 ) + line( -25, 50, -60, 5 ) val boostToLED = line( -120, 10, -150, 10 ) val batteryToCharger = line( 30,0, 60, -10 ) val posLines = plusToSwitch + switchToCharger + chargerToBoost + boostToLED val negLines = batteryToCharger + chargerToBoost.mirrorY() + boostToLED.mirrorY() val text1 = Text( """On Charger : 'B+' goes to the switch 'B-' goes to battery's negative 'out+' goes to boost's 'in+' 'out-' goes to boost's 'in-' """ ).translate(120,40) val text2 = Text( """On Boost : 'out' connects to the LED 'in' connects to the charger's "out" """).translate(-180,-70) return led + switch + boost + battery + charger + posLines.color("Red") + negLines.color("Black") + text1 + text2 } }