import static uk.co.nickthecoder.foocad.extras.v1.Extras.* import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.* import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.* import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.* import static uk.co.nickthecoder.foocad.changefilament.v1.ChangeFilament.* include PuzzleBox.foocad class Solitaire : Model { // Diameter of the balls/marbles, plus a little slack. // 21 is suitable for 20mm balls. var ballSize = 21 // My first print used gap=2, but there needs to be bigger gaps, so that it // is easier to get your fingers in. // Also, this gap makes the pieces the same width as the "ring" and "cups" // so it will fit nicely in the box. var gap = 6.5 var dist = 40 var thickness = 4.0 var thinnest = 0.8 var chamfer = 0.8 var piece = "" var slack = 0.5 fun nobble() : Shape2d { val size = ballSize * 300 val xa = size* 0.11 val xb = size* 0.2 val yb = size* 0.2 val yc = size* 0.35 val delta = size * 0.1 return PolygonBuilder().apply { moveTo( xa, -slack*100 ) bezierTo( Vector2(xa-delta, delta), Vector2(xb, yb-delta), Vector2(xb, yb) ) bezierTo( Vector2(xb, yb+delta), Vector2(delta, yc), Vector2(0, yc) ) bezierTo( Vector2(-delta,yc), Vector2(-xb,yb+delta), Vector2(-xb, yb) ) bezierTo( Vector2(-xb,yb-delta), Vector2(-xa+delta, delta), Vector2(-xa, -slack*100) ) }.build().scale(0.01) } @Piece fun center() : Shape3d { val size = (ballSize + gap ) * 3 val hollows = Sphere( ballSize / 2 ) .tileX( 3, gap ) .tileY( 3, gap ) .center() .translateZ( ballSize/2 + thinnest ) .color("Yellow").darker() val nibble = nobble().offset(slack).mirrorY().translateY(size/2) val square = Square( size-slack ).center().roundAllCorners( 2 ) val solid = ( square - nibble.repeatAround(4) ) .chamferedExtrude( thickness, chamfer ) .color( "Yellow" ) println("Size of solid ${solid.size}" ) return solid - hollows //+ hollows.previewOnly() } @Piece fun side() : Shape3d { val sizeX = (ballSize+gap)*3 val sizeY = (ballSize+gap)*2 val hollows = Sphere( ballSize / 2 ) .tileX( 3, gap ) .tileY( 3, gap ) .centerX() .translateY( -sizeY/4 ) .translateZ( ballSize/2 + thinnest ) .color( "Blue" ) val nobble : Shape2d = nobble().translateY(sizeY/2) val rect = Square( sizeX-slack, sizeY-slack ).center() .roundCorner( 3, 2 ).roundCorner( 2, 2 ) .roundCorner( 1, 5 ).roundCorner( 0, 5 ) val solid = (rect + nobble) .chamferedExtrude( thickness, chamfer ) .color("RoyalBlue") return solid - hollows // + hollows.previewOnly() } @Piece fun triangular() : Shape3d { val size = (ballSize + gap) * 5 + ballSize / 2 val delta = gap val solid = Triangle( size ) .translateY( -ballSize/2 ) .roundAllCorners( ballSize /2 ) .chamferedExtrude( thickness, chamfer ) .color( "RoyalBlue" ) val dy = Degrees.sin(60) * ( ballSize + gap ) val hollow = Sphere( ballSize / 2 ) .translateZ( ballSize/2 + thinnest ) val hollows = hollow.tileX( 5, gap ).centerX() + hollow.tileX( 4, gap ).centerX().translateY( dy ) + hollow.tileX( 3, gap ).centerX().translateY( dy*2 ) + hollow.tileX( 2, gap ).centerX().translateY( dy*3 ) + hollow.translateY( dy*4 ) return solid - hollows // + hollows.previewOnly() } @Piece fun rings() : Shape3d { return Square(ballSize*4).center().roundAllCorners(5) .chamferedExtrude( thickness*2, 0.6 ) - // The 0.5 is so that the balls do not touch each other. Sphere(ballSize/2).tileX(4).tileY(4).center().translateZ(ballSize/2 + thickness +0.5) - Sphere(ballSize/2).tileX(4).tileY(4).center().translateZ(-ballSize/2 + thickness -0.5) - // Rather than do the maths, I've hard coded 0.3 as a resonable radius for the holes. // But if you change the @Custom parameters, then this may not work ;-( Cylinder( ballSize, ballSize*0.3 ).repeatX(4,ballSize).repeatY(4,ballSize).center() } fun createBox( part : String ) : PuzzleBox { return PuzzleBox().apply { width = ballSize * 4 + 1 // Two layers of balls in the "rings" holder a shelf, and 25 for the // jigsaw pieces. depth = (ballSize + thickness * 2) * 2 + thickness + thickness * 4 + 2 bottomHeight = ballSize * 4 - ballSize/2 topHeight = ballSize / 2 + 4 radius = 1 cornerEars = 15 pattern = "none" } } @Piece fun box() : Shape3d { val box = createBox( "bottom" ) val chamfer = box.thickness / 4 val shelf = Square( box.width, box.bottomHeight + thickness + chamfer ) .chamferedExtrude( thickness, thickness/4 ) .center() .rotateX(90) .toOriginZ().toOriginY().translateY( ballSize * 2 + thickness * 2 + 4) .translateZ( box.thickness - chamfer ) val builtBox = box.build() val finger = Cylinder( 1000, 14 ).center().rotateX(90) .translateZ( box.thickness + box.bottomHeight + 4) val previewPieces = Cube( ballSize * 4, 24 /*thickness * 5 + 4*/, ballSize * 4 ) .centerX().translateZ( box.thickness ) .translateY(box.thickness*2 + ballSize*2 + 9 ) val previewStack = Cube( ballSize * 4, 49 /*ballSize*2 + 7*/, ballSize * 4 ) .centerX().translateZ( box.thickness ) .translateY(box.thickness) return builtBox.translateY(box.thickness + box.depth/2) + shelf - finger + previewStack .previewOnly() + previewPieces.previewOnly() + lid() .mirrorZ().toOriginZ().translateZ(box.bottomHeight-4 + 30) .toOriginY() .previewOnly() } @Piece fun lid() : Shape3d { val box = createBox( "top" ) val finger = (Circle( 14-0.5 ) - Square(40).mirrorY().centerX()).extrude( box.thickness ) .rotateX(90) .translateY( - box.depth/2 ) .translateZ( box.thickness + box.topHeight - 4 ) return box.build() + finger.mirrorY().also() } @Piece fun logo() : Shape3d { val dist = ballSize*0.48 val lineThickness = 0.6 val thickness = 1.0 val width = 1 val circ = Circle( 3 ) val circs = circ.repeatX(7, dist).repeatY(3,dist).center().rotate(90).also() val lines = Square(6*dist,width).repeatY(3,dist).center().rotate(90).also() + Square(width,2*dist).repeatX(7,dist).center().rotate(90).also() return circs.extrude( thickness ) + lines.extrude( lineThickness ) } @Piece fun logo2() : Shape3d { val backing = Square( 87,82 ).center().roundAllCorners(2).extrude(0.6) val logo = Text("Solitaire", BOLD, 13).centerX() .translateY(15) .extrude(1.2) + Text( "nickthecoder\n.co.uk", 8).hAlign(CENTER).centerX() .translateY(-20) .extrude(1.2) + Circle( 5 ).tileX( 3, 4 ).center().extrude(1.0) return backing.color("Blue") + logo.color("Yellow") } @Piece fun logo3() : Shape3d { val backing = Square( 87,82 ).center().roundAllCorners(2).extrude(0.6) return backing.color( "Blue" ) + logo().translateZ(0.6).color("Yellow") } override fun build() : Shape3d { val center : Shape3d = center() val side : Shape3d = side() .rotateZ(180) .translateY((ballSize + gap) * 2.5) return center + side.repeatAroundZ(4) } /* // This is the main focus of this example! Also not that the class must implement PostProcessor. override fun postProcess(gcode: GCode) { if (piece == "logo2" || piece == "logo3" ) { pauseAtHeight( gcode, 0.6, "Change Filament" ) } } */ }