Exit Full View
Up

/Games/Penrose2.foocad

Penrose2

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

Sir Roger Penrose gets miffed when people use his designs commercially without his consent. He gets really miffed when people get the tiling wrong!

If you change filament during the printing process, you can create three colour pieces (due to the traces being cut to different depths).

Print Notes

Set Retract Lift to 0 to ensure that changeFilament works correctly.

Use TPU for the lids

Colours

(In case I need reprints)

I used 3DQF's White, Sky Blue, and Navy Blue as well as Eryone's Galaxy Red (for the darts' top layer).

FooCAD Source Code
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" )
            }
        }
    }
*/
}