/Christmas/SpringoTree.foocad
A Springy Christmas Tree decoration. The trunk has a slot for a coin cell (CR2032). No on/off switch. Just take the battery out!
Print Notes
Don't use PLA, because it isn't springy. Rather than buy green PETG, I used transparent, and a green LED to light it ;-)
Assembly
Either bend the LED pins in a contorted manner so that it just sits in place. Or...
Drill holes for wires in the battery connections. Thread bare wires through the holes. Drill 2 holes in the top of the trunk for a regular LED. Solder the LED to the wires. The battery's internal resistance may be ok, but you could add extra resistance if you want.
If the LED is too focused, scratch/sand the lens to spread the light.
When the wiring is done and tested, glue all the pieces together.
import static uk.co.nickthecoder.foocad.circular.v1.Circular.* import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.* import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.* import static uk.co.nickthecoder.foocad.changefilament.v1.ChangeFilament.* import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.* import uk.co.nickthecoder.foocad.threaded.v1.Threaded class SpringoTree : Model, PostProcessor { // If the layers fuse together too much, adjust the gap. // I believe there is "aliasing" between the gap and the layer height. // The gap should be slightly less than a multiple of your layer height. // Look at the print, and you may see some parts have a clear gap, whereas // other places look fused. You want a very small "fused" section. // NOTE. Each slicer may give different results. I use slic3r. @Custom var gap = 0.39 // The height of one piece of the slinky spring (including the gap). // If this is an exact multiple of the layer height, then the alias // artifacts described above line up. // I haven't investigated adjusing this. @Custom var pitch = 2.0 var thickness = 3 var baseSize = Vector2( 60, 1.2 ) var r1 = 30 var r2 = 20 var rounded = 4 var n = 5 var height = 80 var trunkSize = Vector2(18, 24) var trunkThickness = 1.2 // Text on the base @Custom var textStyle = TextStyle(BOLD) @Custom var text1 = "" @Custom var text2 = "" @Custom var twist = 0.0 fun profile( n:int, r1:double, r2:double, rounded:int ) = PolygonBuilder().apply { for ( i in 0 until n*2 ) { val angle = i * 360 / n / 2 val r = if ( i % 2 == 0 ) r1 else r2 val x = r * Degrees.cos(angle) val y = r * Degrees.sin(angle) if (i == 0) { moveTo( x, y ) } else { lineTo( x, y ) } } }.build().roundAllCorners( rounded ) @Piece fun springoTree() : Shape3d { return tree() - Threaded.springo( r1*2, height-10, pitch, gap ).bottomTo(baseSize.y) } @Piece fun star() : Shape3d { val star = profile( 5, 10, 4, 0 ).extrude( 1 ) var attachment = (Cylinder( 10, 3, 0.5 ) - tree().topTo( 10-3 ) ) .rotateY(90).translateX(-10) - Cube( 100 ).centerXY().topTo(0) return star + attachment } fun extrudeTwist( shape : Shape2d, height : double ) : Shape3d { return ExtrusionBuilder().apply { for ( i in 0 .. height ) { crossSection( shape.scale( (height - i)/height ) ) turnZ(twist) forward( 1 ) } }.build() } fun tree() : Shape3d { val profile : Shape2d = profile( n, r1, r2, rounded ) val tree = extrudeTwist( profile, height ).color("Green") val remove = extrudeTwist( profile.offset( -thickness ), height-thickness*height/r1 ).translateZ(-0.01) val hollowTree = tree - remove val base = (profile - Circle( trunkSize.x/2 + 0.3 )).extrude( baseSize.y ) return (hollowTree + base ).color("Green") } @Piece fun trunk() : Shape3d { val flat = (profile(n,r1-7,r2-5,rounded-2)-Circle(3)).extrude( baseSize.y ) val trunk = Cylinder( trunkSize.y, trunkSize.x/2 ) - Cylinder( trunkSize.y*3, trunkSize.x/2 - trunkThickness ).center() val batterySlot = Cube( 4, 21, 21 ).centerX().bottomTo( trunkThickness ) return (flat + trunk - batterySlot).color("Brown") } @Piece fun connectors() : Shape3d { val thickness = 1 val h = 10 val r = trunkSize.x/2 - trunkThickness - 0.3 val ring = (Circle( r ) - Circle(r - thickness)).leftTo(1.3) val outsideP = Circle( r ) - Circle( r - thickness ) val profile = ring / Circle( r ) + outsideP - Square( r*1.2 ).center().frontTo(r*0.5) val connector = profile.extrude( h ) val connectors = connector.mirrorX().also() return connectors } @Piece fun base() : Shape3d { val base = profile( 10, baseSize.x/2, baseSize.x/2 * 0.8, 6 ).extrude( baseSize.y ) val circle = Circle( trunkSize.x/2 - trunkThickness - 0.3 ) val trunk = (circle - circle.offset( -trunkThickness*1.5 )).extrude(10) val batterySlot = Cube( 4, 21, 21 ).centerX().bottomTo( baseSize.y ) val merry = Text( text1, textStyle ).center() .aroundCircle( baseSize.x * 0.3 ) val xmas = Text( text2, textStyle ).center() .aroundCircle( -baseSize.x * 0.3 ) val text = (merry + xmas).extrude( baseSize.y ).bottomTo( baseSize.y ) .color( "GhostWhite" ) val main = (base + trunk - batterySlot).color( "Green" ) return main + text } override fun build() : Shape3d { val base : Shape3d = base() val trunk : Shape3d = trunk().mirrorZ().bottomTo(baseSize.y) val tree : Shape3d = tree().bottomTo(trunk.top) val connectors : Shape3d = connectors().bottomTo(base.top) val battery = Circle(10).chamferedExtrude( 3.2, 0, 0.7 ).rotateY(90).previewOnly() .bottomTo( baseSize.y+1 ).centerX().frontTo(-5) val star : Shape3d = star().rotateY(-90).bottomTo( tree.top - 4 ) return tree + trunk + base + connectors + battery + star } override fun postProcess( gcode: GCode ) { } override fun postProcess( piece : String, gcode: GCode ) { if ( (text1!="" || text2 !="") && piece.startsWith( "base" ) ) { pauseAtHeight( gcode, baseSize.y, "Change Filament" ) } } }