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 uk.co.nickthecoder.foocad.smartextrusion.v1.* import static uk.co.nickthecoder.foocad.smartextrusion.v1.SmartExtrusion.* import uk.co.nickthecoder.foocad.cup.v1.* import static uk.co.nickthecoder.foocad.cup.v1.Cup.* class GiftTube : AbstractModel() { @Custom( about="Internal diameter and height of the tube" ) var size = Vector2( 30, 50 ) @Custom var lidHeight = 10 @Custom var outerThickness = 3.5 @Custom var innerThickness = 1.7 @Custom var clearance = 0.3 @Custom var pinClearance = 0.1 @Custom var grooveSize = 3.6 @Custom( about="Resolution of the maze's curves. Warning too large causes problems. Check with F6 in OpenSCAD" ) var steps = 2 @Custom( about="Size of the maze (the number of columns)") var mazeWidth = 10 @Custom( about="Size of the maze (the number of rows)" ) var mazeHeight = 9 @Custom( about="The number of pins which travel through the maze" ) var repeats = 2 @Custom( lines=10 ) var routes = """ -5, 1, RRRRRRRRRRRRRR +0, 1, UULLLULLUUURRRULL -1, 1, ULLLU +3, 1, UR +5, 1, UULLULUULUURR +0, 3, ULLURRRDDRDL -3, 4, UURRRUU -4, 4, UU +1, 7, RRRUR +2, 5, RUR -5, 5, LD -2, 8, RD 0, 8, UU """ meth gridHeight() = size.y / (mazeHeight +0.5) meth gridWidth() = (size.x * Math.PI) / mazeWidth / repeats meth paths() : List { val result = listOf() val scale = Vector2( gridWidth(), gridHeight() ) for ( line in routes.split("\n") ) { if (line.isBlank()) || line.trim().startsWith("//" ) continue val parts = line.split( "," ) if (parts.size() != 3) { println( "Illegal route. Expected x,y,ROUTE, found : `$line`" ) continue } val x = parts[0].parseFloat() val y = parts[1].parseFloat() val directions = parts[2].trim() val builder = PolygonBuilder().apply { moveTo( x,y ) } for (dir in directions.toUpperCase()) { if (dir == 'U') builder.lineBy( 0, 1 ) if (dir == 'D') builder.lineBy( 0, -1 ) if (dir == 'L') for (step in 0 until steps ) { builder.lineBy( -1/steps, 0 ) } if (dir == 'R') for (step in 0 until steps ) { builder.lineBy( 1/steps, 0 ) } } result.add( builder.buildPath().scale( scale ).translateY(outerThickness) ) } return result } @Piece( printable=false ) meth flatMaze() : Shape3d { val profile = Circle( grooveSize/2 ).sides(4) .roundCorner(2,1,1) .roundCorner(0,1,1) val altProfile = profile.rotate(90) var result : Shape3d = null for (path in paths()) { val path3d = path.to3d() val dy = path.points[0].y - path.points[1].y val useProfile = if (dy==0) profile else altProfile val shape = useProfile.extrude(path3d) result = shape + result } return result.rotateX(90) } @Piece( printable=false ) meth curvedMaze() : Shape3d { return flatMaze().transform(AroundCylinder( size.x /2, false)) .repeatAroundZ(repeats) } @Piece meth innerTube() : Shape3d { val lid = Circle( size.x / 2 + outerThickness ) .smartExtrude( lidHeight ) .bottom( Chamfer(outerThickness/2) ) val tube = Circle( size.x/2 - clearance ) .smartExtrude( size.y - clearance*2 ) .bottomTo( lid.top ) val hollow = Circle( tube.size.x / 2 - innerThickness ) .extrude( size.y + lidHeight + 1 ) .bottomTo( outerThickness ) val pinHeight = lid.top + (mazeHeight - 0.5) * gridHeight() - clearance*0 val pin = Cylinder( grooveSize/2/1.4, grooveSize / 2, grooveSize/2 - 1 ) .rotateX(90) .translateZ( pinHeight ) .backTo( tube.front + pinClearance ) .repeatAroundZ( repeats ) .color("Green") return tube + lid + pin - hollow } @Piece meth outerTube() : Shape3d { val shape = Circle( size.x / 2 + outerThickness ) val main = shape.cup( size.y + outerThickness - clearance, outerThickness ) .outsideBottom( Chamfer(outerThickness/2) ) .color( "Orange" ) val maze = curvedMaze() return main - maze } @Piece( printable=false ) meth together() : Shape3d { val outer = outerTube().rotateZ(-30) val inner = innerTube() return outer + inner.mirrorZ().topTo( outer.top + lidHeight + clearance) } @Piece( printable=false ) meth test() : Shape3d { val flatMaze = flatMaze().translateX(-150) val curvedMaze = curvedMaze().mirrorX() val cyl = Circle( size.x /2 ).extrude( curvedMaze.top ) .color("WHITE") return flatMaze + curvedMaze + cyl } override meth build() : Shape3d { val outer = outerTube() val inner = innerTube() val together = outer + inner.mirrorZ().bottomTo( outerThickness + clearance ) val payload = Cylinder( 10, 25/2 ).centerZTo( inner.top ) .previewOnly() return arrangeY( arrangeX( outer, inner + payload ).centerXY(), together.previewOnly() ) } }