Digital Sundial
A sundial block that shows the hour as a pattern of light dots. For every hour from 9 to 15, thin holes are drilled in the shape of that hour's digits on a 3x5 dot font, each hole tilted exactly toward the sun's direction at that time, so only the matching digits light up on the surface below. All 134 holes are placed and angled by calculation in the code. The block measures 27 x 61 x 20 mm and prints flat with the display face down, without supports; use an opaque material and 100% infill so no light leaks through. It is designed for one solar declination (equinox by default), and a separate stand is needed to align it with Earth's axis. You can change the first and last hour, the design-day declination, block thickness, pixel pitch and hole widths.
# Digital Sundial (simplified) - thin holes tilted toward the sun for each hour, one per digit pixel
# When the sun is at hour angle theta, light passes only through the holes parallel to theta,
# and the dots of that hour's digits appear on the surface below.
#
# Coordinates: X = polar (Earth's axis) direction, Z = direction of the noon sun in the equatorial plane, Y = east-west.
# The bottom face z = 0 is the "display", light enters through the top face z = depth.
# In use, a separate stand is needed to hold the X axis parallel to Earth's axis (pointed north, tilted by the latitude).
@param 6..12 desc:"First hour to display (h)"
t_first = 9
@param 12..18 desc:"Last hour to display (h)"
t_last = 15
@param -23.4..23.4 step:0.1 desc:"Solar declination of the design day (deg, equinox = 0)"
decl = 0.0
@param 12..30 desc:"Block thickness = hole length (mm)"
depth = 20
@param 2.5..4 step:0.1 desc:"Pixel pitch (mm)"
pitch = 3.0
@param 1.0..2.4 step:0.1 desc:"Hole width, east-west = time selectivity (mm)"
hole_y = 1.6
@param 1.5..3.5 step:0.1 desc:"Hole width along the polar axis (mm)"
hole_x = 2.4
# 3x5 dot font. Pixel index k = row * 3 + column (row 0 at top). Only lit pixels are listed
font = [
[0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 13, 14], # 0
[1, 3, 4, 7, 10, 12, 13, 14], # 1
[0, 1, 2, 5, 6, 7, 8, 9, 12, 13, 14], # 2
[0, 1, 2, 5, 6, 7, 8, 11, 12, 13, 14], # 3
[0, 2, 3, 5, 6, 7, 8, 11, 14], # 4
[0, 1, 2, 3, 6, 7, 8, 11, 12, 13, 14], # 5
[0, 1, 2, 3, 6, 7, 8, 9, 11, 12, 13, 14], # 6
[0, 1, 2, 5, 8, 11, 14], # 7
[0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14], # 8
[0, 1, 2, 3, 5, 6, 7, 8, 11, 12, 13, 14], # 9
[] # 10 = blank (tens digit of a one-digit hour)
]
def hour_angle(t) = 15 * (t - 12) # noon = 0 deg, 15 deg per hour
def tens(t) = if t < 10 then 10 else floor(t / 10)
def dz(t) = cos(decl) * cos(hour_angle(t)) # Z component of the sun direction
# Hole for hour t, digit j (0 = tens, 1 = ones), pixel k.
# Tilting a Z-oriented box by the declination around Y and by the hour angle around X points it at the sun
# (sin d, -cos d*sin theta, cos d*cos theta). Its center is shifted so it passes through the bottom pixel (px, py, 0)
def px(j, k) = (j * 4 + k % 3 - 3) * pitch
def py(k) = (2 - floor(k / 3)) * pitch
def ray_hole(t, j, k) = box hole_x hole_y ((depth + 4) / dz(t))
| rotate 0 decl 0
| rotate (hour_angle(t)) 0 0
| translate (px(j, k) + depth / 2 * sin(decl) / dz(t)) (py(k) - depth / 2 * cos(decl) * sin(hour_angle(t)) / dz(t)) (depth / 2)
def digit_holes(t, j, d) = union [ray_hole(t, j, k) for k in font[d]]
times = range(t_first, t_last + 1)
holes = union [
union [digit_holes(t, 0, tens(t)) for t in times],
union [digit_holes(t, 1, t % 10) for t in times]
]
# Block: large enough to include the entry of the most tilted hole, plus a 3mm margin
reach_y = depth * tan(max(abs(hour_angle(t_first)), abs(hour_angle(t_last))))
reach_x = depth * abs(tan(decl)) / cos(max(abs(hour_angle(t_first)), abs(hour_angle(t_last))))
blk_x = 7 * pitch + 2 * reach_x + 6
blk_y = 5 * pitch + 2 * reach_y + 6
box blk_x blk_y depth | floor | diff holes