/Games/Penrose.foocad

See [https://en.wikipedia.org/wiki/Penrose_tiling]
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*
class Penrose : AbstractModel(), PostProcessor {
// The size of the long edge
@Custom
var size : double = 50.0
@Custom
var separateTraces = true
@Custom
var chamfer = 0.5
var traceElevation = 0.6
var baseHeight = 0.6 // 2.0 for "Solid" pieces
var ridgeHeight = 2.0 // -0.5 for "Solid" pieces
var ridgeThickness = 3.0 // 0.5 for "Solid" pieces
// The width of the traces (Green in the wikipedia diagram)
// https://en.wikipedia.org/wiki/File:Kite_Dart.svg
@Custom
var traceAThickness = 1.5
@Custom
var traceAHeight = 3
// The width of the other traces (Red in the wikipedia diagram)
// https://en.wikipedia.org/wiki/File:Kite_Dart.svg
@Custom
var traceBThickness = 2
@Custom
var traceBHeight = 2.0
@Custom
var ratioA = 0.45
@Custom
var ratioB = 0.45
@Custom
var slack = 0.7 // Was 0.5 Was 0.3
// margin, bedWidth and bedDepth are used when printing multiple
// copies of a shape
@Custom
val margin = 2
@Custom
val bedWidth = 170
@Custom
val bedDepth = 170
// 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 getShortSize() : 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
}
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
}
fun main( shape : Shape2d ) : Shape3d {
return ExtrusionBuilder().apply {
crossSection( shape.offset( -chamfer ) )
forward(chamfer)
crossSection( shape )
forward( baseHeight + ridgeHeight - chamfer )
crossSection()
//crossSection( shape.offset( -ridgeThickness ) )
forward( - ridgeHeight )
crossSection( shape.offset( -ridgeThickness ) )
}.build()
}
fun kiteTraceA( cutting : bool ) : Shape3d {
val shape = kiteShape()
val a = size*(1-ratioA)
val addSlack = if (cutting) slack else 0.0
val traceAShape = (
Circle(a+traceAThickness/2 + addSlack) -
Circle(a-traceAThickness/2 - addSlack)
).intersection( shape )
return traceAShape
.extrude(traceAHeight)
.translateZ(traceElevation)
.color("Black")
}
fun kiteTraceB( cutting : bool ) : Shape3d {
val shape = kiteShape()
val b = shortSize*(1-ratioB)
val addSlack = if (cutting) slack else 0.0
val traceBShape = (
Circle(b+traceBThickness/2 + addSlack) -
Circle(b-traceBThickness/2 - addSlack)
).translateX(shape.right).intersection( shape )
return traceBShape
.extrude(traceBHeight)
.translateZ(traceElevation)
.color("White")
}
@Piece
fun kite() : Shape3d {
val shape = kiteShape()
val main : Shape3d = main(shape).color("DarkGreen")
return if ( separateTraces ) {
main - kiteTraceA(true) - kiteTraceB(true)
} else {
main + kiteTraceA(false) + kiteTraceB(false)
}
}
fun dartTraceA(cutting : bool) : Shape3d {
val shape = dartShape()
val a = size*ratioA
val addSlack = if (cutting) slack else 0.0
val traceAShape = (
Circle(a+traceAThickness/2 + addSlack) -
Circle(a-traceAThickness/2 - addSlack)
).translateX(shape.right).intersection( shape )
return traceAShape
.extrude(traceAHeight)
.translateZ(traceElevation)
.color("Black")
}
fun dartTraceB(cutting : bool) : Shape3d {
val shape = dartShape()
val b = shortSize*ratioB
val addSlack = if (cutting) slack else 0.0
val traceBShape = (
Circle(b+traceBThickness/2 + addSlack ) -
Circle(b-traceBThickness/2 - addSlack )
).intersection( shape )
val height = if (cutting) {
traceBHeight + slack/2
} else {
traceBHeight
}
return traceBShape
.extrude(traceBHeight)
.translateZ(traceElevation)
.color("White")
}
@Piece
fun dart() : Shape3d {
val shape = dartShape()
var main : Shape3d = main(shape).color("LightGreen")
return if ( separateTraces ) {
main - dartTraceA(true) - dartTraceB(true)
} else {
main + dartTraceA(false) + dartTraceB(false)
}
}
// A single trace
@Piece
fun dartTraceA() = (dartTraceA(false) - dartTraceB( true )).mirrorZ().centerXY().toOriginZ()
@Piece
fun kiteTraceA() = kiteTraceA(false)
@Piece
fun dartTraceB() = kiteTraceB(false)
@Piece
fun dartTraceAs() = (dartTraceA(false) - dartTraceB( true )).mirrorZ().centerXY().toOriginZ()
.tileXWithin(bedWidth/2,-size*0.35+margin)
.tileY(2,margin)
//.tileYWithin(bedDepth,margin)
.center()
@Piece
fun dartTraceBs() = dartTraceB(false)
.tileXWithin(bedWidth,-size*0.15+margin)
.tileY(2,margin)
//.tileYWithin(bedDepth,margin)
.center()
@Piece
fun kiteTraceAs() = kiteTraceA(false)
.tileXWithin(bedWidth/2,-size*0.4+margin)
.tileY(2,margin)
//.tileYWithin(bedDepth,margin)
.center()
@Piece
fun kitTraceBs() = kiteTraceB(false)
.tileXWithin(bedWidth*0.6,-size*0.15+margin)
.tileY(2,margin)
//.tileYWithin(bedDepth,margin)
.center()
@Piece
fun darts() = dart()
.tileXWithin(bedWidth,-size*0.15+margin)
.tileYWithin(bedDepth,margin)
.center()
@Piece
fun kites() = kite()
.tileXWithin(bedWidth,margin)
.tileYWithin(bedDepth,margin)
.center()
@Piece( printable = false )
override fun build() : Shape3d {
// Replicates the 7 possible vertex combinations :
// https://upload.wikimedia.org/wikipedia/commons/2/26/Penrose_vertex_figures.svg
val dart : Shape3d = dart()
val kite : Shape3d = kite()
val gap = 0.0
val a = dart.translateX(-dart.right-gap).rotateZ(18).repeatAroundZ(5)
val b = kite.rotateZ(36+18).translateX(gap/2).mirrorX().also() +
dart.rotateZ(90).translateY(size+gap)
val c = kite.translateX(gap).rotateZ(18).repeatAroundZ(5)
val d = kite.translateX(-kite.right-gap).rotateZ(-18).mirrorX().also() +
dart.translateX(-dart.right-gap).rotateZ(-18).repeatAroundZ(3,-72).translateY(shortSize+gap)
val e = kite.rotateZ(-18*3).translate(gap,-gap,0).mirrorX().also() +
dart.translate(-dart.left,-dart.front,0).rotateZ(-18*3).mirrorX().also() +
kite.translateX(-size).rotateZ(-90)
val f = kite.translateX(gap).rotateZ(54).mirrorX().also() +
dart.rotateZ(-90).translateY(dart.right+size+gap*2) +
kite.rotateZ(-90).translate(size*0.59,size*1.81,0).mirrorX().also()
val g = kite.translateX(-kite.size.x).rotateZ(18).translateX(-gap/2).mirrorX().also() +
dart.rotateZ(18*3).translate(-size*0.365, size*0.5,0).mirrorX().also()
return a +
b.translate(size*2,-size*0.7,0) +
c.translateX(size*4) +
d.translate(-size*2,-size*3,0) +
e.translate(size/2, -size*2.5,0) +
f.translate(size*3, -size*3.5,0) +
g.translate(size*5.5, -size*3,0)
}
// The length of the filament from the hot end to the closest point
// that it can be cut (and another filament fed through).
// Used by postProcess to add gcode message to change the filament.
// This is printer dependant!
@Custom
var filamentOffset = 45
/**
Add GCode messages to advise the operator to change the filament.
*/
override fun postProcess(gcode: GCode) {
if ( ! separateTraces ) {
//val e1 = gcode.findHeight( baseHeight + ridgeHeight ).e
//val state1 = gcode.findExtrusion(e1 - filamentOffset)
// .insertAfter( "M300 S300 P1000\nM117 Change Filament A" )
//gcode.insertAtTime(state1.seconds - 30, "M300 S300 P1000\nM117 Change in 30s..." )
//val e2 = gcode.findHeight( Math.min(traceAThickness,traceAThickness) ).
//val state2 = gcode.insertAtExtrusion(e2 - filamentOffset, "M300 S300 P1000\nM117 Change Filament B" )
//gcode.insertAtTime(state2.seconds - 30, "M300 S300 P1000\nM117 Change in 30s..." )
}
}
}

