# Apartment: a 66 m² two-room flat in a city block, as the planner would draw it — a kitchen-living
# room with a balcony, a bedroom with a walk-in closet, a hall and a bathroom, every piece of furniture
# from the library at its real size. The plan with its checks is docs/apartment/plan.py: north = +y,
# the windows and the balcony to the south, the front door on the landing to the north. Ceiling 2.70.
from math import *
units("cm")

def one(parts, name):
    # (many small parts kept as one solid: joined in pairs, then pairs of pairs)
    l = list(parts)
    while len(l) > 1:
        nl = [l[i] + l[i + 1] for i in range(0, len(l) - 1, 2)]
        if len(l) % 2:
            nl.append(l[-1])
        l = nl
    l[0].name = name
    return l[0]

plaster, facade, cut = "#f1eee8", "#e4ded3", "#cdc7bc"
white, oak, dark, steel = "#f4f2ee", "#c49a6c", "#2f3033", "#c9ccd2"
H = 270                                     # floor to ceiling
Z0 = -2                                     # the walls stand on the slab, the floors' finish laid between them

# ---------- the slab and the floors ----------
box(1160, 680, 20, at=(-20, -40, Z0 - 20), name="floor slab", color="#bdb8ae")
landing = box(300, 160, 22, at=(600, 640, Z0 - 20), name="landing", color="#d3cec4 tile")
def floor(rects, name, finish):
    parts = [box(x1 - x0, y1 - y0, 2, at=(x0, y0, Z0)) for x0, y0, x1, y1 in rects]
    f = one(parts, name) if len(parts) > 1 else parts[0]
    f.name = name
    f.paint(finish)
    return f
floor([(0, 0, 500, 620), (500, 390, 640, 620)], "living room floor", "oak planks")
floor([(510, 0, 1100, 190), (770, 190, 1100, 380)], "bedroom floor", "oak planks")
floor([(510, 200, 760, 380)], "closet floor", "oak planks")
floor([(650, 390, 860, 620)], "hall floor", "#b7b2a8 tile")
floor([(870, 390, 1100, 620)], "bathroom floor", "#c4c7c9 tile")

# ---------- the walls: each its own solid, so each room's side of it has its own finish ----------
TILE = "#eef0ef tile"
def wall(name, x0, y0, x1, y1, faces=None):
    w = box(x1 - x0, y1 - y0, H - Z0, at=(x0, y0, Z0), name=name, color=plaster)
    w.top.paint(cut)
    for side, paint in (faces or {}).items():
        if side == "front": w.front.paint(paint)
        if side == "back": w.back.paint(paint)
        if side == "left": w.left.paint(paint)
        if side == "right": w.right.paint(paint)
    return w
south = wall("south wall", -20, -40, 1140, 0, {"front": facade})
west = wall("wall to the neighbours", -20, 0, 0, 620)
east_a = wall("east wall", 1100, 0, 1140, 390, {"right": facade})
east_b = wall("east wall, bathroom", 1100, 390, 1140, 620, {"right": facade, "left": TILE})
north_a = wall("wall to the landing", -20, 620, 645, 640)
north_b = wall("wall to the landing, hall", 645, 620, 865, 640)
north_c = wall("wall to the landing, bathroom", 865, 620, 1140, 640, {"front": TILE})
p_living = wall("partition living | bedroom", 500, 0, 510, 380)
p_hall = wall("partition living | hall", 640, 390, 650, 620)
p_rooms = wall("partition rooms | hall", 500, 380, 870, 390)
p_bath = wall("partition bedroom | bathroom", 870, 380, 1100, 390, {"back": TILE})
p_hb = wall("partition hall | bathroom", 860, 390, 870, 620, {"right": TILE})
c_south = wall("closet wall", 510, 190, 770, 200)
c_east = wall("closet wall, east", 760, 200, 770, 380)

# ---------- windows (sill 85, 150 high), the balcony door, the doors ----------
# W2 and the balcony door are one block, their tops level: the balcony runs past both its ends
for x, w in ((60, 160), (265, 130), (550, 180), (840, 180)):
    window(w=w, h=150, at=(x, -40, 85), wall=south)
    lip = w + 5 if x == 265 else w + 10            # (the block's sill stops at the door)
    one([box(w, 32, 3, at=(x, -32, 85)), box(lip, 5, 3, at=(x - 5, 0, 85))], "window sill").paint(white)
    box(w - 20, 10, 50, at=(x + 10, 0, 15), name="radiator", color=white)
window(w=90, h=235, panes=1, at=(395, -40, 0), wall=south)          # the balcony door, glazed
def door_in(wl, x, y, turn, w, open, hinge, swing, color, frame):
    # a door whose frame stands at (x, y) in the wall: at= is the corner of all of it as it stands,
    # handles and an open leaf too, so where the frame falls in that is found on a trial one first
    t = door(w=w, h=210, turn=turn, open=open, hinge=hinge, swing=swing, at=(0, 0, 0))
    fx, fy = t.part("frame top").min[0], t.part("frame top").min[1]
    t.delete()
    return door(w=w, h=210, turn=turn, open=open, hinge=hinge, swing=swing, color=color, frame=frame, at=(x - fx, y - fy, 0), wall=wl)
door_in(north_b, 700, 630, 180, 90, 0, "left", "out", "#4a4440", "#4a4440")     # the front door, shut
# the inner doors stand open, each the way the plan swings it
door_in(p_hall, 640, 500, 90, 90, 90, "right", "in", white, white)             # hall -> living room
door_in(p_rooms, 770, 380, 0, 90, 80, "left", "out", white, white)             # hall -> bedroom (its handle clear of the closet wall)
door_in(p_hb, 860, 400, 90, 80, 90, "left", "in", white, white)                # hall -> bathroom (out into the hall)
door_in(c_east, 760, 210, 90, 80, 90, "right", "in", white, white)             # bedroom -> closet

# ---------- the kitchen along the north wall: fridge | sink | worktop | hob | worktop ----------
fridge(w=60, d=65, h=185, at=(0, 550, 0))
kitchen_units(w=360, d=60, sink="left", hob="middle", color="#e6e2da", top="#c49a6c wood", at=(60, 557.2, 0))
wall_units(w=180, d=35, h=70, lift=155, color="#e6e2da", at=(60, 583.8, 155))
wall_units(w=120, d=35, h=70, lift=155, color="#e6e2da", at=(300, 583.8, 155))
hood = [box(60, 50, 10, at=(240, 570, 155), name="hood", color=steel),
        box(60, 36.2, 60, at=(240, 583.8, 165), name="hood casing", color="#e6e2da")]
one(hood, "hood")
box(360, 1, 65, at=(60, 619, 90), name="splashback", color="#f3f1ec tile")
sideboard(w=120, d=45, h=80, color="#5e4231", at=(440, 575, 0))

# ---------- dining: a table for four ----------
dining_table(w=160, d=90, color=oak, at=(120, 315, 0))
for x in (140, 215):
    chair(color=oak, pad="#8a8f99", at=(x, 410, 0))
    chair(color=oak, pad="#8a8f99", at=(x, 260, 0), turn=180)

# ---------- living: the TV on the west wall, the sofa facing it ----------
tv_unit(w=180, d=40, h=50, color="#e6e2da", at=(0, 60, 0), turn=90)
box(4, 122, 70, at=(0, 89, 85), name="TV", color="#16171a")
rug(w=200, d=160, color="#b9a68c", border="#e6dfd2", at=(130, 50, 0), turn=90)
coffee_table(w=100, d=55, h=42, color=oak, at=(210, 100, 1.2), turn=90)
sofa(w=220, d=92, color="#7d8590", at=(300, 40, 0), turn=270)
floor_lamp(at=(345, 265, 0))
bookcase(w=160, d=30, h=200, color=oak, at=(470, 120, 0), turn=270)

def houseplant(cx, cy, h, r, name):
    pot = cone(r * 0.78, r, r * 1.5, at=(cx, cy, 0), sides=20)
    pot.paint("#d8d2c8")
    parts = [pot, cylinder(r * 0.9, 1, at=(cx, cy, r * 1.5), color="#4a3828", sides=20),
             cylinder(1.6, h * 0.5, at=(cx, cy, r * 1.5 + 1), color="#6b4f3a", sides=8)]
    top = r * 1.5 + h * 0.5
    for dx, dy, dz, rr, c in ((0, 0, 0, r * 1.15, "#4f7a45"), (r * 0.6, 0, -r * 0.5, r * 0.8, "#5d8a4f"),
                              (-r * 0.5, r * 0.4, -r * 0.3, r * 0.85, "#5d8a4f"), (0, -r * 0.6, r * 0.5, r * 0.75, "#4f7a45")):
        parts.append(sphere(rr, at=(cx + dx, cy + dy, top + dz), color=c, sides=12))
    return one(parts, name)
houseplant(30, 277, 110, 22, "plant")
houseplant(610, 425, 90, 22, "plant")

# ---------- bedroom: the bed's head to the east wall, a desk in the bay by the window ----------
bed(w=160, l=200, color="#8f9aa6", linen="#e6dfd2", at=(888, 106, 0), turn=270)
bedside_table(color=oak, at=(1060, 274, 0), turn=270)
bedside_table(color=oak, at=(1060, 61, 0), turn=270)
rug(w=150, d=60, color="#8f9aa6", border="#e6dfd2", at=(900, 280, 0))
desk(w=140, d=70, drawers="right", color=oak, at=(570, 10, 0), turn=180)
chair(color=oak, pad="#8f9aa6", at=(617, 85, 0))

# ---------- the walk-in closet: open shelves, a hanging rail with clothes, a mirror ----------
bookcase(w=120, d=45, h=220, books=False, color=white, at=(510, 200, 0), turn=90)
rail = [box(2, 60, 220, at=(515, 320, 0)), box(2, 60, 220, at=(753, 320, 0)),
        box(236, 60, 2, at=(517, 320, 198)), box(236, 60, 2, at=(517, 320, 12)),
        pipe([(517, 350, 172), (753, 350, 172)], 1.5, color=steel, sides=12)]
CL = ["#3d4a5c", "#8a8f99", "#c9c2b6", "#2f3033", "#7d9c88", "#a85c3b", "#e6dfd2", "#5e6b7a", "#b9a68c"]
for k in range(30):
    L = (70, 105, 90, 60, 110, 75)[k % 6]
    rail.append(box(2.5, 48, L, at=(524 + k * 7.4, 326, 171.5 - L), color=CL[(k * 4) % len(CL)]))
for p in rail[:4]:
    p.paint(white)
one(rail, "hanging rail")
box(60, 2, 170, at=(580, 200, 30), name="mirror", color="#cfd8dc")

# ---------- hall ----------
wardrobe(w=110, d=60, h=240, color=white, at=(800, 510, 0), turn=270)
chest_of_drawers(w=80, d=35, h=85, color=oak, at=(650, 400, 0), turn=90)
rug(w=80, d=50, color="#5e6b7a", border="#3d4a5c", at=(705, 562, 0))

# ---------- bathroom ----------
bathtub(w=170, d=75, taps="left", at=(930, 545, 0))
washing_machine(at=(870, 557.6, 0))
toilet(at=(1033.5, 425, 0), turn=270)
washbasin(w=60, d=46, unit=oak, at=(890, 390, 0), turn=180)
box(50, 2, 70, at=(895, 390, 110), name="mirror", color="#cfd8dc")

# ---------- the balcony ----------
box(260, 120, 18, at=(245, -160, Z0 - 18), name="balcony slab", color="#bdb8ae")
box(260, 120, 2, at=(245, -160, Z0), name="balcony floor", color="#c9c2b5 tile")
railing(w=260, h=110, style="bars", at=(245, -160, 0))
railing(w=114, h=110, style="bars", at=(245, -154, 0), turn=90)
railing(w=114, h=110, style="bars", at=(499, -154, 0), turn=270)
for x in (312, 423):
    chair(color="#2f3033", pad="#7d9c88", at=(x, -110, 0))
coffee_table(w=50, d=40, h=45, shelf=False, color="#2f3033", at=(365, -105, 0))

# ---------- what's on the walls: switches and sockets, wall lights, curtains, pictures ----------
# wall(axis, face, side): axis "x" — the wall's face is at x = face, the room on its +x side (side 1)
# or -x side (side -1); axis "y" the same along y. c: where along the wall, z: the height of its middle.
def plate(axis, face, side, c, z, w, h, t, color):
    if axis == "x":
        return box(t, w, h, at=(face if side > 0 else face - t, c - w / 2, z - h / 2), color=color)
    return box(w, t, h, at=(c - w / 2, face if side > 0 else face - t, z - h / 2), color=color)

def socket(axis, face, side, c, z=30, n=1):
    W = 8 + 7 * (n - 1)
    parts = [plate(axis, face, side, c, z, W, 8, 1.2, "#f6f5f2")]
    for k in range(n):
        parts.append(plate(axis, face + side * 1.2, side, c - W / 2 + 4 + 7 * k, z, 4.6, 4.6, 0.4, "#d6d6d2"))
    return one(parts, "socket")

def switch(axis, face, side, c, z=90):
    return one([plate(axis, face, side, c, z, 8, 8, 1.2, "#f6f5f2"),
                plate(axis, face + side * 1.2, side, c, z, 5.5, 6, 0.6, "#e4e3df")], "light switch")

def sconce(axis, face, side, c, z):
    # a wall light: a plate, an arm out of it and a fabric shade, its middle 16 cm off the wall
    parts = [plate(axis, face, side, c, z + 2, 10, 14, 2, "#b08d57"),
             plate(axis, face + side * 2, side, c, z + 3, 2, 2, 12, "#b08d57")]
    m = face + side * 16
    parts.append(cone(8, 5, 15, at=((m, c, z - 6) if axis == "x" else (c, m, z - 6)), color="#efe6d2", sides=20))
    return one(parts, "wall light")

def curtains(x0, x1, panels, color, name):
    # a rod on brackets across the window wall (south, y = 0) and panels hung from it, in folds
    parts = [pipe([(x0, 14, 248), (x1, 14, 248)], 1.2, color="#2b2b2e", sides=10)]
    for bx in (x0 + 8, (x0 + x1) / 2, x1 - 8):
        parts.append(box(2, 14, 3, at=(bx - 1, 0, 246.5), color="#2b2b2e"))
    for a, b in panels:
        n = max(2, round((b - a) / 6))
        front = [(a + (b - a) * i / n, 12 + (3 if i % 2 else 0)) for i in range(n + 1)]
        back = [(x, y + 1.5) for x, y in reversed(front)]
        parts.append(prism(front + back, 245, plane="xy", offset=2, color=color))
    return one(parts, name)

def poster(axis, face, side, u0, z0, w, h, design, frame="#222326"):
    # a framed print: the frame, a white mount, and the picture's shapes laid on it, each a layer
    parts = [plate(axis, face, side, u0 + w / 2, z0 + h / 2, w, h, 2.5, frame),
             plate(axis, face + side * 2.5, side, u0 + w / 2, z0 + h / 2, w - 5, h - 5, 0.3, "#f6f3ec")]
    m = 7
    pw, ph = w - 2 * m, h - 2 * m
    k = [0]
    def lay(pts, color):
        d = 2.8 + 0.25 * k[0]
        k[0] += 1
        off = face + d if side > 0 else face - d - 0.25
        P = [(u0 + m + u, z0 + m + v) for u, v in pts]
        parts.append(prism(P, 0.25, plane=("yz" if axis == "x" else "xz"), offset=off, color=color))
    def rect(u0_, v0, u1, v1, color):
        lay([(u0_, v0), (u1, v0), (u1, v1), (u0_, v1)], color)
    def circle(cu, cv, r, color):
        lay([(cu + r * cos(radians(a)), cv + r * sin(radians(a))) for a in range(0, 360, 12)], color)
    T, R, M, G, S, C = "#2f6f73", "#d9643a", "#e3b448", "#5f8a6a", "#2f5d62", "#f1e4d3"
    if design == "sun":
        rect(0, 0, pw, ph, C)
        rect(0, 0, pw, ph * 0.32, T)
        rect(0, ph * 0.32, pw, ph * 0.37, M)
        circle(pw * 0.5, ph * 0.62, pw * 0.27, R)
    elif design == "blocks":
        rect(pw * 0.08, ph * 0.5, pw * 0.62, ph * 0.92, M)
        rect(pw * 0.4, ph * 0.08, pw * 0.92, ph * 0.58, T)
        circle(pw * 0.3, ph * 0.28, pw * 0.16, R)
    elif design == "hills":
        rect(0, 0, pw, ph, "#dfe8e6")
        lay([(0, 0), (pw, 0), (pw, ph * 0.42), (pw * 0.62, ph * 0.66), (pw * 0.32, ph * 0.44), (0, ph * 0.58)], G)
        lay([(0, 0), (pw, 0), (pw, ph * 0.22), (pw * 0.7, ph * 0.4), (pw * 0.42, ph * 0.24), (0, ph * 0.34)], S)
        circle(pw * 0.78, ph * 0.76, ph * 0.12, M)
    elif design == "leaf":
        rect(0, 0, pw, ph, C)
        L = [(pw * 0.5 + pw * 0.34 * sin(radians(a)) * cos(radians(35)) - ph * 0.42 * cos(radians(a)) * sin(radians(35)) * 0.8,
              ph * 0.5 + pw * 0.34 * sin(radians(a)) * sin(radians(35)) * 0.6 + ph * 0.42 * cos(radians(a)) * cos(radians(35)))
             for a in range(0, 360, 10)]
        lay(L, G)
        circle(pw * 0.78, ph * 0.18, pw * 0.08, R)
    elif design == "arch":
        rect(0, 0, pw, ph, "#ecd3c1")
        lay([(pw * 0.2, 0), (pw * 0.8, 0)] + [(pw * 0.5 + pw * 0.3 * cos(radians(a)), ph * 0.55 + pw * 0.3 * sin(radians(a))) for a in range(0, 181, 10)], "#c4553a")
        circle(pw * 0.5, ph * 0.84, pw * 0.07, T)
    return one(parts, "picture")

# switches by the doors, on the side away from the hinge; sockets low (30), over the worktop (110),
# by the beds (70), at the desk (85), by the basin (110)
switch("x", 640, -1, 604)                   # the living room's, by the door from the hall
switch("y", 620, -1, 686)                   # the hall's, by the front door
switch("x", 860, -1, 494)                   # the bathroom's, in the hall by its door
switch("y", 380, -1, 872)                   # the bedroom's, by its door
switch("x", 760, -1, 304)                   # the closet's, inside
socket("y", 619, -1, 150, 110, 2)           # over the worktop
socket("y", 619, -1, 370, 110, 2)
socket("x", 0, 1, 254)                      # by the TV
socket("x", 0, 1, 470)                      # by the table
socket("y", 0, 1, 235)                      # by the sofa, between the radiators
socket("y", 390, 1, 580)                    # by the sideboard
socket("x", 1100, -1, 84, 70)               # by the bed, both sides
socket("x", 1100, -1, 296, 70)
socket("x", 510, 1, 64, 85)                 # at the desk
socket("y", 380, -1, 1004)
socket("y", 620, -1, 660)                   # in the hall
socket("y", 390, 1, 964, 110)               # by the basin
# wall lights: either side of the TV, over each bedside table
sconce("x", 0, 1, 60, 150)
sconce("x", 0, 1, 240, 150)
sconce("x", 1100, -1, 84, 140)
sconce("x", 1100, -1, 296, 140)
box(50, 3, 4, at=(895, 390, 186), name="light over the mirror", color="#efe6d2")
# curtains: sage in the living room (one rod along the wall: windows and the balcony door), dusty blue in the bedroom
curtains(10, 497, [(12, 62), (214, 266), (487, 498)], "#8a9a7b", "curtains")
curtains(512, 768, [(514, 560), (722, 766)], "#7a8fa6", "curtains")
curtains(790, 1070, [(792, 846), (1014, 1068)], "#7a8fa6", "curtains")
# pictures
poster("x", 0, 1, 300, 125, 60, 80, "sun")                  # over the table
poster("x", 0, 1, 380, 125, 60, 80, "blocks")
poster("y", 390, 1, 515, 125, 60, 60, "leaf", frame=oak)    # by the sideboard
poster("x", 1100, -1, 140, 130, 100, 60, "hills", frame=oak)   # over the bed
poster("y", 380, -1, 930, 120, 80, 60, "arch")
poster("y", 190, -1, 590, 120, 60, 80, "blocks", frame=oak)    # in the bay, over the desk
# a mirror in the hall over the shoe cabinet
one([box(2.5, 74, 104, at=(650, 403, 98), color=oak), box(0.5, 68, 98, at=(652.5, 406, 101), color="#cfd8dc")], "mirror")
