FooCAD Source Codeimport 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 )
}
}