Exit Full View
ZipUp

/Torch/CobTwist.foocad

CobTwist

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.
FooCAD Source Code
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*

class CobTwist : AbstractModel() {
  
    // 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 ====
    @Piece( printable = false )
    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
    }

}