import static uk.co.nickthecoder.foocad.changefilament.v1.ChangeFilament.* import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.* import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.* import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.* import static uk.co.nickthecoder.foocad.layout.Layout2d.* import static uk.co.nickthecoder.foocad.layout.Layout3d.* import static uk.co.nickthecoder.foocad.target.GCodePostProcessing.* class Penrose2 : Model { @Custom( about="The size of the long edge" ) var size : double = 40.0 @Custom( about="Overrall thickness of the pieces" ) var thickness = 2.2 @Custom( about="The depth that the curved traces" ) var traceThickness = 0.8 @Custom( about="Radius of the (blue) curves set as a ratio of the side's length" ) var traceARatio = 0.4 @Custom( about="Radius of the (white) curves set as a ratio of the side's length" ) var traceBRatio = 0.35 @Custom( about="The width of the (blue) curves" ) var traceAWidth = 10.0 @Custom( about="The width of the (white) curves" ) var traceBWidth = 3.0 @Custom( about="0 for no traces. 1 for top side only, 2 for both sides" ) // Double sided doesn't work well, as you would need support material... var tracesOnSides = 1 @Custom( about="Add gcode to pause the printer for colour changes?" ) var changeFilament = true @Custom( about="As well as making the pieces feel nicer, a chamfer can eliminate elephants foot problems" ) var bottomChamfer = 0.9 var topChamfer = 0.6 @Custom( about="How much smaller are the pieces, so that they fit together easily" ) // You can think of this as half the width of the grout when using ceramic tiles. var slack = 0.3 @Custom( about="Blunt the corners, for a better fit and nicer feeling pieces" ) var round = 1.0 var circleSides = 60 // Trig values used to calculate the geometry of the pieces. val s36 = Degrees.sin(36) val c36 = Degrees.cos(36) val s72 = Degrees.sin(72) val c72 = Degrees.cos(72) fun longSize() = size fun shortSize() : double { return size * s36 / s72 } fun kiteShape() : Shape2d { val a = c36 * size val y = size * s36 val shape = PolygonBuilder().apply { moveTo(0,0) lineTo(a, y) lineTo(size, 0) lineTo(a,-y) close() }.build() return shape.offset( -slack ).roundAllCorners( round ).color("LightBlue") } fun dartShape() : Shape2d { val y = size * s36 val d = shortSize() * c72 val e = size * c36 val shape = PolygonBuilder().apply { moveTo(0,0) lineTo(-d, y) lineTo( e-d,0 ) lineTo(-d, -y) close() }.build() return shape.offset( -slack ).roundAllCorners( round ).color("Yellow") } fun carve( shape : Shape2d, cA : double, cB : double, traceARatio : double, traceBRatio : double ) : Shape3d { val traceASize = longSize() * traceARatio val traceBSize = shortSize() * traceBRatio var solid = shape.chamferedExtrude(thickness, bottomChamfer, topChamfer).color(shape.color) val traceA = ( Circle( traceASize + traceAWidth / 2 ).sides(circleSides) - Circle( traceASize - traceAWidth / 2 ).sides(circleSides) ).translateX(cA) .extrude( traceThickness +1 ).bottomTo( thickness - traceThickness ) .color("DarkBlue") val traceB = ( Circle( traceBSize + traceBWidth / 2 ).sides(circleSides) - Circle( traceBSize - traceBWidth / 2 ).sides(circleSides) ).translateX(cB) .extrude( traceThickness +1 ).bottomTo( thickness - traceThickness * 2 ) .color("White") if (tracesOnSides > 0) { solid -= traceA + traceB if (tracesOnSides == 2) { solid -= traceA.topTo( traceThickness ) + traceB.topTo( traceThickness * 2 ) } } return solid } @Piece fun kite() : Shape3d { return carve( kiteShape(), 0, size, 1 - traceARatio, 1-traceBRatio ) } @Piece fun dart() : Shape3d { val d = shortSize() * c72 val e = size * c36 return carve( dartShape(), e-d, 0, traceARatio, traceBRatio ) } @Piece fun kiteAndDart() = kite() + dart().translateX(size+1) @Piece fun kite5() = kite().translateX(1).repeatAroundZ(5) @Piece fun dart5() = dart().translateX(-shortSize()-1).repeatAroundZ(5).rotateZ(72/4) var holderGap = 2 @Piece fun kiteHolder() : Shape3d { val height = 10 * (thickness+0.2) val sides = 10 val spokeLength = 8 val spoke = Circle( holderGap/2 ).sides(sides) hull Circle( holderGap / 2 ).sides(sides).translateX( spokeLength ) val spokes = spoke.rotate(72/2).repeatAround(5).extrude( height ) val corner = (spoke.translateX(size-spokeLength+3) + spoke.rotate(180-72).translateX(size+3).mirrorY().also() ).rotate(72/2) val corners = corner.repeatAround(5) val corners3d = corners.chamferedExtrude( height, bottomChamfer, 0 ) val base = (Hull2d( corners ) + kiteShape().offset(2).repeatAround(5) - kiteShape().offset(-5).repeatAround(5) ).chamferedExtrude( thickness, bottomChamfer, topChamfer ) val kite : Shape3d = kite().translate(2, 0, thickness).previewOnly() return kite + spokes + corners3d + base } // Print this in TPU, for a tight fitting rubbery lid @Piece fun kiteLid() : Shape3d { val tight = 0.5 val lidThickness = 1.2 val inside = Hull2d( kiteShape().offset(holderGap+1+lidThickness- tight).repeatAround(5) ) val middle = inside.offset( -6 ) val outside = inside.offset( lidThickness ) val spokes = Square( size, 6 ).centerY().repeatAround(5).rotate(36) val base = (outside-middle + spokes).chamferedExtrude( lidThickness, bottomChamfer, 0) val lid = base + (outside - inside).extrude(6).bottomTo( lidThickness ) return lid + kiteHolder().mirrorZ().bottomTo(lidThickness).previewOnly() } @Piece fun dartHolder() : Shape3d { val height = 10 * (thickness+0.2) val sides = 10 val spokeLength = 10 val dartShape : Shape2d = dartShape().translateX(-shortSize()).rotate(180) val dart : Shape3d = dart().translate(-shortSize()-2, 0, thickness).rotateZ(180).previewOnly() val spoke = Circle( holderGap/2 ).sides(sides) hull Circle( holderGap / 2 ).sides(sides).translateX( spokeLength ) val spokes = spoke.rotate(72/2).repeatAround(5).extrude( height ) val corner = (spoke.translateX(size-spokeLength+5) + spoke.rotate(-72*2).translateX(size+5).mirrorY().also() ).rotate(72/2) val corners = corner.repeatAround(5) val corners3d = corners.chamferedExtrude( height, bottomChamfer, 0 ) val base = ( (dartShape.offset(4) + Hull2d(corner)).repeatAround(5) - dartShape.offset(-3).repeatAround(5) ).chamferedExtrude( thickness, bottomChamfer, topChamfer ) return dart + spokes + corners3d + base } // Print this in TPU, for a tight fitting rubbery lid @Piece fun dartLid() : Shape3d { val tight = 0.3 val lidThickness = 1.2 val spokeLength = size+6 val spoke = Circle( holderGap/2 ) hull Circle( holderGap / 2 ).translateX( spokeLength ) val corner = (spoke.translateX(size-spokeLength+5) + spoke.rotate(-72*2).translateX(size+5).mirrorY().also() ).rotate(72/2) val corners = corner.repeatAround(5) val inside = Hull2d( corner ).repeatAround(5) val middle = inside.offset( -6 ) val outside = inside.offset( lidThickness ) val spokes = Square( size, 6 ).centerY().repeatAround(5).rotate(36) val base = (outside-middle + spokes).chamferedExtrude( lidThickness, bottomChamfer, 0) val lid = base + (outside - inside).extrude(6).bottomTo( lidThickness ) return lid + dartHolder().mirrorZ().bottomTo(lidThickness).previewOnly() } override fun build() : Shape3d { val kite : Shape3d = kite() val darts = dart().translateX(size).rotateZ(72/2).mirrorY().also() val extraKites = kite.rotateZ(72/2).translateX(size).mirrorY().also() return kite.repeatAroundZ(5) + darts + extraKites } /* override fun postProcess( piece : String, gcode : GCode ) { if ( !piece.contains("Holder") && !piece.contains("Lid") && changeFilament) { // Start with the colour of the bottom face if (tracesOnSides>1) { pauseAtHeight( gcode, traceThickness, "Bottom Trace A" ) pauseAtHeight( gcode, traceThickness*2, "Bottom Trace B" ) // NOTE. No need to change again if the traces on both sides are the same color // pauseAtHeight( gcode, thickness/2, "TopTrace B" ) } if (tracesOnSides > 0) { pauseAtHeight( gcode, thickness - traceThickness, "TopTrace A" ) pauseAtHeight( gcode, thickness - traceThickness*2, "Top Face" ) } } } */ }