Polyhedral Dice Set
A d4, d6, d8, d12 and d20, each built as the intersection of one slab per face with the slab normals taken from the Platonic solid, so the whole shape comes from a list of face normals. The faces are numbered in opposite pairs that add up to faces plus one, every number is engraved on its face, and 6 and 9 carry a dot. Choose one die or the whole set in a row. The default is 16 mm across flats; the set is laid out 96 x 19 x 16 mm with every die resting on face 1 for printing. The d4, d6, d8 and d12 print without supports; the d20's upper faces overhang at about 48 degrees, so give it a brim or supports. Adjustable parameters: die, size, engraving depth and edge rounding.
# Polyhedral Dice Set - d4, d6, d8, d12 and d20 built as intersections of half-spaces
#
# Each die is the intersection of one slab per face, the slab's normal taken from the
# Platonic solid's face normals, so the shape comes from the list of normals and nothing
# else. The faces are numbered in opposite pairs (opposite faces add up to faces + 1) and
# each number is engraved on its face; 6 and 9 carry a dot. `die` picks one die or the
# whole set in a row. Every die is laid down on face 1 for printing; the d20's upper
# faces overhang at about 48 deg, so give it a brim or supports.
@param choices:["set", "d4", "d6", "d8", "d12", "d20"] desc:"Which die, or the whole set"
$die = "set"
@param 12..25 step:1 desc:"Size across flats (mm); the d4 is this tall"
$size = 16
@param 0.3..1 step:0.1 desc:"Engraving depth (mm)"
$depth = 0.6
@param 0.5..2 step:0.25 desc:"Edge rounding (mm)"
$round = 1
$phi = (1 + sqrt(5)) / 2
$p = 1 / $phi
# face normals, listed in opposite pairs (n, -n)
$x6 = [0, 0, 1, -1, 0, 0]
$y6 = [0, 0, 0, 0, 1, -1]
$z6 = [1, -1, 0, 0, 0, 0]
$x8 = [1, -1, 1, -1, 1, -1, -1, 1]
$y8 = [1, -1, 1, -1, -1, 1, 1, -1]
$z8 = [1, -1, -1, 1, 1, -1, 1, -1]
$x12 = [0, 0, 0, 0, 1, -1, 1, -1, $phi, -$phi, $phi, -$phi]
$y12 = [1, -1, 1, -1, $phi, -$phi, -$phi, $phi, 0, 0, 0, 0]
$z12 = [$phi, -$phi, -$phi, $phi, 0, 0, 0, 0, 1, -1, -1, 1]
$x20 = [1, -1, 1, -1, 1, -1, 1, -1, 0, 0, 0, 0, $p, -$p, $p, -$p, $phi, -$phi, $phi, -$phi]
$y20 = [1, -1, 1, -1, -1, 1, -1, 1, $p, -$p, $p, -$p, $phi, -$phi, -$phi, $phi, 0, 0, 0, 0]
$z20 = [1, -1, -1, 1, 1, -1, -1, 1, $phi, -$phi, -$phi, $phi, 0, 0, 0, 0, $p, -$p, -$p, $p]
$x4 = [1, 1, -1, -1]
$y4 = [1, -1, 1, -1]
$z4 = [1, -1, -1, 1]
# a slab whose top face is at distance r from the origin with outward normal (x, y, z)
$big = 10 * $size
def slab($x, $y, $z, $r) = box $big $big $big | translate 0 0 (-$big / 2 + $r)
| rotate 0 (acos($z / sqrt($x * $x + $y * $y + $z * $z))) (atan2($y, $x))
# an engraved number on that face: text at the origin, raised to the face and turned with it
def digit($s, $h, $x, $y, $z, $r) = text $s $h | extrude ($depth + 1) | translate 0 0 ($r - $depth)
| rotate 0 (acos($z / sqrt($x * $x + $y * $y + $z * $z))) (atan2($y, $x))
# face number i of an F-faced die: opposite faces add up to F + 1
def num($i, $f) = if $i % 2 == 0 then $i / 2 + 1 else $f - ($i - 1) / 2
def label($i, $f) = (if num($i, $f) == 6 or num($i, $f) == 9 then ("" + num($i, $f) + ".") else ("" + num($i, $f)))
def die6($r, $h) = (inter [slab($x6[$i], $y6[$i], $z6[$i], $r) for $i in range(6)] | fillet $round)
- (union [digit(label($i, 6), $h, $x6[$i], $y6[$i], $z6[$i], $r) for $i in range(6)])
def die8($r, $h) = (inter [slab($x8[$i], $y8[$i], $z8[$i], $r) for $i in range(8)] | fillet $round)
- (union [digit(label($i, 8), $h, $x8[$i], $y8[$i], $z8[$i], $r) for $i in range(8)])
def die12($r, $h) = (inter [slab($x12[$i], $y12[$i], $z12[$i], $r) for $i in range(12)] | fillet $round)
- (union [digit(label($i, 12), $h, $x12[$i], $y12[$i], $z12[$i], $r) for $i in range(12)])
def die20($r, $h) = (inter [slab($x20[$i], $y20[$i], $z20[$i], $r) for $i in range(20)] | fillet $round)
- (union [digit(label($i, 20), $h, $x20[$i], $y20[$i], $z20[$i], $r) for $i in range(20)])
def die4($r, $h) = (inter [slab($x4[$i], $y4[$i], $z4[$i], $r) for $i in range(4)] | fillet $round)
- (union [digit("" + ($i + 1), $h, $x4[$i], $y4[$i], $z4[$i], $r) for $i in range(4)])
# inradius r and text height from the size; text height from the inradius of the face polygon
$r6 = $size / 2
$r8 = $size / 2
$r12 = $size / 2
$r20 = $size / 2
$r4 = $size / 4
$d6 = die6($r6, 0.7 * $r6)
$d8 = die8($r8, 0.75 * 0.707 * $r8)
$d12 = die12($r12, 0.8 * 0.618 * $r12)
$d20 = die20($r20, 0.8 * 0.382 * $r20)
$d4 = die4($r4, 0.6 * 1.41 * $r4)
# lay each die on its face 1 (normal n0 -> -Z) and put it on the bed
def lay($d, $x, $y, $z, $dx) = $d | rotate 0 0 (-atan2($y, $x)) | rotate 0 (-acos($z / sqrt($x * $x + $y * $y + $z * $z))) 0
| rotate 180 0 0 | floor | translate $dx 0 0
$p4 = lay($d4, 1, 1, 1, -2.4 * $size)
$p6 = lay($d6, 0, 0, 1, -1.2 * $size)
$p8 = lay($d8, 1, 1, 1, 0)
$p12 = lay($d12, 0, 1, $phi, 1.3 * $size)
$p20 = lay($d20, 1, 1, 1, 2.7 * $size)
assert $d6.solids == 1
assert $d8.solids == 1
assert $d12.solids == 1
assert $d20.solids == 1
assert $d4.solids == 1
assert $p6.bbox.z ~ $size tol:0.05
assert $p8.bbox.z ~ $size tol:0.05
assert $p12.bbox.z ~ $size tol:0.05
assert $p20.bbox.z ~ $size tol:0.05
assert $p4.bbox.z ~ $size tol:2.5 # the rounded apex takes about 2 mm off a tetrahedron
# a cube of size across flats has exactly this volume before rounding and engraving
assert $d6.volume < $size ** 3
assert $d6.volume > 0.93 * $size ** 3
$set = $p4 + $p6 + $p8 + $p12 + $p20
assert $set.solids == 5
print "set" $set.bbox
$out = if $die == "set" then $set else if $die == "d4" then (lay($d4, 1, 1, 1, 0)) else if $die == "d6" then (lay($d6, 0, 0, 1, 0)) else if $die == "d8" then (lay($d8, 1, 1, 1, 0)) else if $die == "d12" then (lay($d12, 0, 1, $phi, 0)) else (lay($d20, 1, 1, 1, 0))
$out