# Spiral staircase: fourteen oak treads round a steel column, each turned 22.5° from the last and
# rising one step; balusters at their ends, a handrail swept along a helix, the floor above with
# its opening and a glass balustrade.
from math import *
units("cm")

N, TURN, RISE = 14, 22.5, 20        # treads, degrees per tread, the step's rise
R_COL, R_OUT, T = 9, 105, 5         # column radius, the treads' outer radius, their thickness
FLOOR = (N + 1) * RISE              # the upper floor: 300
A0 = 135                            # where the first tread starts: the stair arrives at the back, at 90°
oak, steel, rail_c = "oak", "#2f3033", "walnut"

def sector(r0, r1, a0, a1, n=8):
    # a ring sector's outline, as (x, y)
    outer = [(r1 * cos(radians(a0 + (a1 - a0) * i / n)), r1 * sin(radians(a0 + (a1 - a0) * i / n))) for i in range(n + 1)]
    m = n if r1 - r0 < 20 else 2      # (a thin band follows the arc inside too)
    inner = [(r0 * cos(radians(a1 - (a1 - a0) * i / m)), r0 * sin(radians(a1 - (a1 - a0) * i / m))) for i in range(m + 1)]
    return outer + inner

column = cylinder(R_COL, FLOOR, name="column", color=steel, sides=48)
cap = cylinder(R_COL + 3, 3, at=(0, 0, FLOOR - 3), name="column cap", color=steel, sides=48)
steps, balusters = [], []
for i in range(N):
    a0 = A0 + i * TURN
    z = (i + 1) * RISE - T
    tread = prism(sector(R_COL - 1, R_OUT, a0, a0 + TURN + 2), T, offset=z, name=f"tread {i + 1}", color=oak)
    tread.fillet(1.2, edges="top")
    # a steel bracket under each tread, out from the column
    am = radians(a0 + TURN / 2)
    bracket = prism([(R_COL - 1, z), (R_OUT - 25, z), (R_COL - 1, z - 14)], 1.5, plane="xz", offset=-0.75, name="bracket", color=steel)
    bracket.rotate(a0 + TURN / 2, about=(0, 0, 0))
    a = radians(a0 + TURN * 0.75)
    post = cylinder(1.2, 92, at=((R_OUT - 5) * cos(a), (R_OUT - 5) * sin(a), z + T), name="baluster", color=steel, sides=12)
    steps.append(group([tread, bracket], name=f"step {i + 1}"))
    balusters.append(post)

# the handrail: a helix over the balusters' tops, then level round the landing
path = []
for k in range(N * 4 + 1):
    t = k / 4
    a = radians(A0 + TURN * (t + 0.75))
    path.append(((R_OUT - 5) * cos(a), (R_OUT - 5) * sin(a), RISE * (t + 1) + 92))
rail = pipe(path, 2.6, smooth=True, name="handrail", color=rail_c, sides=16)

# the floors: below, and above with its round opening (cut short in front, to see in)
ground = box(520, 520, 10, at=(-260, -260, -10), name="floor", color="#d8cfc0")
upper = box(520, 290, 22, at=(-260, -40, FLOOR - 22), name="upper floor", color="plaster")
upper = upper - cylinder(R_OUT + 13, 22, at=(0, 0, FLOOR - 22), sides=96)
upper.top.paint("#c49a6c")
# the landing: the next sector, at the floor's level, out to the floor
landing = prism(sector(R_COL - 1, R_OUT + 14, 90, 140), 6, offset=FLOOR - 6, name="landing", color=oak)
landing.fillet(1.2, edges="top")
# glass panes round the opening where the floor is (not where the stair comes up), in a steel shoe
glass, metal = [], []
for b0, b1 in [(-20, 64), (140, 200)]:
    n = round((b1 - b0) / 28)
    for k in range(n):
        a0 = b0 + (b1 - b0) * k / n
        glass.append(prism(sector(R_OUT + 13.6, R_OUT + 14.8, a0 + 0.3, a0 + (b1 - b0) / n - 0.3, 6), 96, offset=FLOOR + 4, name="glass", color="glass"))
    metal.append(prism(sector(R_OUT + 13, R_OUT + 16, b0, b1, 24), 6, offset=FLOOR, name="shoe", color=steel))
    metal.append(prism(sector(R_OUT + 13.4, R_OUT + 15.4, b0, b1, 24), 3, offset=FLOOR + 100, name="top rail", color=steel))

group([column, cap] + steps + balusters + [rail, landing], name="staircase")
group([upper] + metal + glass, name="upper floor")
group([ground], name="floor")
dim((R_OUT + 40, -40, 0), (R_OUT + 40, -40, FLOOR), offset=(40, 0, 0))
a = radians(A0 + 5 * TURN)
label("14 treads, 22.5° and 20 cm each", at=(-230, -170, 130), to=(R_OUT * 0.8 * cos(a), R_OUT * 0.8 * sin(a), 6 * RISE))
