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