Exit Full View
Up

/Games/Penrose.foocad

Penrose

See [https://en.wikipedia.org/wiki/Penrose_tiling]

FooCAD Source Code
import static uk.co.nickthecoder.foocad.layout.v1.Layout2d.*
import static uk.co.nickthecoder.foocad.layout.v1.Layout3d.*

class Penrose : Model, 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()

    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..." )
        }
    }

}