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