Exit Full View
Up

/Words/JigsawStencil.foocad

JigsawStencil
FooCAD Source Code
import static uk.co.nickthecoder.foocad.chamferedextrude.v1.ChamferedExtrude.*


/**
    A stencil to trace individual letters.
    Each letter has jigsaw style bobbles, so that words can be formed before tracing them.

    Printed using 3dQF purple
*/
class JigsawStencil : Model {

// I, d, p, o, l, e, a, m

    @Custom( about="Letters to include" )
    var letters = "em"

    @Custom( about="Thickness of the stencil" )
    var thickness = 0.8 //2.0

    @Custom( about="Thickness of the outlines" )
    var outlineThickness = 2.0


    @Custom( about="The margin around the words, forming the rectangular stencil" )
    var margin = 18

    @Custom( about="Radius of the stencil's corners" )
    var radius = 10
    
    @Custom
    var bobbleSlack = 0.4


    // NOTE, these cannot be @Custom, because 'width', 'outlineWidth' and 'joinSize.x' must
    // match the SVG file for everything to line up correctly. :-(

    // The width of the stokes
    var width = 3
    var outlineWidth = 2.0
    // "Width and height of the joining parts, which prevent the middle of letters being detatched" )
    var joinSize = Vector2(2, 0.9)

    var clipT = 1
    var clipHeight = 26

    @Piece
    fun holder() : Shape3d {
    var doc = SVGParser().parseFile("alphabetStencil.svg")

        val clipHalf = doc.shapes["clipHalf"].paths[0].thickness(clipT)
            .toOrigin().translate(-clipT,0)
        val handle = doc.shapes["handle"].toOrigin().translateY(-0.1)
            .mirrorY().also().rightTo(0)
        val base = doc.shapes["bottom"].toOrigin().leftTo(0).centerYTo(0)
        val end = Circle(2).centerTo( clipHalf.right, clipHalf.back )
        val clip2d = (clipHalf + end ).mirrorY().also()
        val clip = clip2d.extrude( clipHeight )
        val bobble = doc.shapes["halfBobble"].toOrigin()
            .mirrorY().also().extrude(1).previewOnly()

        val firstLayer = (clip2d + base).boundingSquare()
            .offset(6).roundAllCorners(6).extrude(0.3)

        return firstLayer + bobble + clip + base.extrude(0.9) + handle.extrude(clipHeight)
    }

    override fun build() : Shape3d {
        var all : Shape3d = Cube(0)
        
        var x = 0.0
        var y = 0.0
        for (letter in letters) {
            val one = buildLetter("$letter").translate( x, y, 0 )
            all = all + one
            //x += one.right + bobbleSlack // To see how the jigsaw shapes line up.
            x += one.size.x + 6 // Gives enough room, so that each piece is separate.
            if ( x > 280 ) {
                x = 0
                y -= 110
            }
        }
        return all
    }

    fun buildLetter( letter : String) : Shape3d {
        println("Building letter $letter" )

        var doc = SVGParser().parseFile("alphabetStencil.svg")

        val baseline = doc.shapes["baseline"]
        val group = doc.shapes[letter]
        val baselineOffset = baseline.back

        var vertical = doc.shapes["${letter}V"]
        var horizontal = doc.shapes["${letter}H"]

        var lines : Shape2d = Union2d()
        if (group is Union2d) {
            for shape in (group as Union2d).dependencies2d {
                for path in shape.paths {
                    lines += path.thickness( width )
                }
            }
        } else {
            for path in group.paths {
                lines += path.thickness( width )
            }
        }


        val join = doc.shapes["bobbleJoin"].size.x
        val halfBobble = doc.shapes["halfBobble"]

        val background = lines.boundingSquare().offset( margin ).translateX(margin*0.18).roundAllCorners(radius)


        // Some letters have "holes" such as "a" and "o", so we need to add small joins
        // so that the "hole" doesn't fall out ;-)
        val joinVertical = if (vertical == null) {
            Cube(0)
        } else {
            vertical.paths[0].thickness(joinSize.x)
                .extrude( joinSize.y ).color("Red")
        }
        val joinHorizontal = if (horizontal == null ) {
            Cube(0)
        } else {
            horizontal.paths[0].thickness(joinSize.x)
                .extrude( joinSize.y ).color("Red")
        }


        val bobble = halfBobble.rightTo( background.left + join ).frontTo(-1).mirrorY().also().centerYTo( baseline.back + 16 )
        val antiBobble = bobble.rightTo( background.right + join ).centerYTo( baseline.back + 16 )

        val piece = background - antiBobble + bobble.offset( -bobbleSlack )

        val solid = if (thickness == outlineThickness) {
            piece.chamferedExtrude( thickness, 0.4, 0.8 )
        } else {
            val flat = piece.extrude(thickness) + joinVertical + joinHorizontal
            val outline = (piece - piece.offset( -outlineWidth )).extrude( outlineThickness )
            val inline = lines.offset( outlineWidth ).extrude( outlineThickness )
            flat + outline + inline
        }

        val finished = solid - lines.extrude( 20 ).centerZ() +
            joinVertical + joinHorizontal

        return finished.translateX(-background.left).translateY( -baseline.back )
    }
}