# Desk lamp: a turned base, two pairs of arms posed with a little trigonometry, coil springs swept
# along helices, a shade turned from a profile, shelled and tilted; on a desk with a notebook.
from math import *
units("mm")

shell_c, brass, dark, bulb_c = "#2b2e33", "#c9a54a", "#2f3033", "#fff3d1"

def turn_profile(pts, name, color, sides=72):
    return polygon(pts, plane="xz").revolve(axis="z", name=name, color=color, sides=sides)

def along(p, d, t):
    return (p[0] + d[0] * t, p[1] + d[1] * t, p[2] + d[2] * t)

def unit(a):
    return (cos(radians(a)), 0, sin(radians(a)))

# ---------- the base ----------
base = turn_profile([(0, 2), (94, 2), (96, 4), (96, 12), (92, 18), (70, 25), (40, 29), (24, 33), (17, 37), (17, 46), (0, 46)], "base", shell_c)
pad = cylinder(93, 2, at=(0, 0, 0), name="felt", color="#6b4a3a")
yoke = []
for y in (-14, 10):
    plate = box(16, 4, 34, at=(-8, y, 42), name="yoke", color=brass)
    plate.fillet(3, edges="y")
    yoke.append(plate)

# ---------- the arms ----------
P0 = (0, 0, 68)                     # the base joint
D1 = unit(108)                      # the lower arm, leaning back a little
L1 = 310
P1 = along(P0, D1, L1)              # the elbow
D2 = unit(-14)                      # the upper arm, reaching forward
L2 = 290
P2 = along(P1, D2, L2)              # the head joint

def rods(p, d, l, name):
    out = []
    for y in (-9, 9):
        r = cylinder(3.5, l, at=(p[0], y, p[2]), axis=d, name=name, color=brass, sides=20)
        out.append(r)
    return out

def joint(p, r, name):
    j = cylinder(r, 30, at=(p[0], -15, p[2]), axis="y", name=name, color=brass, sides=32)
    j.chamfer(1)
    k = cylinder(r * 0.8, 5, at=(p[0], 15, p[2]), axis="y", name="knob", color=dark, sides=32)
    k.fillet(1.5, edges=k.face("y"))
    return [j, k]

lower = rods(P0, D1, L1, "lower arm")
upper = rods(P1, D2, L2, "upper arm")
joints = joint(P0, 9, "base joint") + joint(P1, 10, "elbow") + joint(P2, 8, "head joint")

# two coil springs beside the lower arm: a helix, swept
springs = []
for y in (-20, 20):
    pts = []
    turns, rr, length = 16, 4.5, 130
    nx, nz = -D1[2], D1[0]                         # across the arm, in its plane
    for i in range(turns * 10 + 1):
        a = 2 * pi * i / 10
        t = 40 + length * i / (turns * 10)
        c = along(P0, D1, t)
        pts.append((c[0] + rr * cos(a) * nx, y + rr * sin(a), c[2] + rr * cos(a) * nz))
    springs.append(pipe(pts, 1.1, name="spring", color="#9a9da3", sides=10))
    for t in (40, 170):
        c = along(P0, D1, t)
        springs.append(cylinder(1.4, abs(y) - 9, at=(c[0], 9 if y > 0 else -9, c[2]), axis=(0, 1 if y > 0 else -1, 0), name="spring hook", color="#9a9da3", sides=12))

# ---------- the head ----------
shade = turn_profile([(0, 0), (20, 0), (30, -4), (44, -16), (56, -38), (70, -84), (74, -104), (0, -104)], "shade", shell_c, 72)
shade.shell(1.6, open="bottom")
socket = cylinder(17, 30, at=(0, 0, -30), name="socket", color=dark, sides=32)
bulb = sphere(30, at=(0, 0, -62), name="bulb", color=bulb_c, sides=48)
neck = cylinder(6, 16, at=(0, 0, -2), name="neck", color=brass, sides=24)
head = [shade, socket, bulb, neck]
for p in head:
    p.rotate(-38, axis="y", about=(0, 0, 0))
    p.move(P2[0] + 16, 0, P2[2] - 14)

# the cable: out of the base's back, along the arms, into the shade
# the cable: out of the base's back, up between the arms' rods, into the shade
def beside(p, d, t, off=5):
    c = along(p, d, t)
    return (c[0] - d[2] * off, 0, c[2] + d[0] * off)
cable = pipe([(-14, 0, 40), (-24, 0, 58), beside(P0, D1, 40), beside(P0, D1, L1 - 30), beside(P1, D2, 25), beside(P1, D2, L2 - 30), (P2[0] + 8, 0, P2[2] - 8)], 2, smooth=True, name="cable", color=dark, sides=12)
cord = pipe([(-80, 0, 3.2), (-170, 30, 3.2), (-260, 90, 3.2), (-300, 200, 3.2)], 3, smooth=True, name="cord", color=dark, sides=12)
plug = box(18, 22, 6, at=(-82, -11, 0.2), name="cord grip", color=dark)
plug.fillet(2.5)

lamp = group([group([base, pad, plug] + yoke, name="base"), group(lower + upper + joints + springs, name="arms"), group(head, name="head"), cable, cord], name="desk lamp")
lamp.move(-40, 60, 0)

# ---------- the desk, a notebook, a pencil ----------
desk = box(820, 500, 28, at=(-400, -220, -28), name="desk", color="oak")
desk.fillet(4, edges="top")
# a notebook: covers and the pages between them
cover = box(210, 148, 1.6, at=(0, 0, 0), name="cover", color="#33507a")
pages = box(204, 144, 7, at=(4, 2, 1.6), name="pages", color="#f4f2ec")
cover2 = box(210, 148, 1.6, at=(0, 0, 8.6), name="cover", color="#33507a")
spine = box(6, 148, 10.2, at=(-2, 0, 0), name="spine", color="#33507a")
spine.fillet(2.9, edges="y")
book = group([cover, pages, cover2, spine], name="notebook")
book.rotate(-14, about=(0, 0, 0))
book.move(110, -190, 0)
# a pencil on the desk beside it
pencil = ngon(0, 0, 3.6, sides=6, plane="yz", offset=0).pull(150, name="pencil", color="#f2cf4a")
tip = cone(3.1, 0.5, 18, at=(150, 0, 0), axis="x", name="tip", color="#e3c9a0", sides=12)
eraser = cylinder(3.1, 12, at=(-12, 0, 0), axis="x", name="eraser", color="#e481b0", sides=12)
ferrule = cylinder(3.3, 5, at=(-3, 0, 0), axis="x", name="ferrule", color="silver", sides=12)
for p in (pencil, tip, eraser, ferrule):
    p.rotate(24, about=(0, 0, 0))
    p.move(215, 30, 3.12)
group([pencil, tip, eraser, ferrule], name="pencil")
group([desk], name="desk")
