# 에펠탑 (프랑스 파리, 1889) — 실제 크기(높이 330m) 코드 모델.
# 탑의 바깥 윤곽 W(z)와 다리 안쪽 모서리 I(z)를 단조 3차 보간(PCHIP) 곡선으로 정의하고,
# 네 다리를 '네 개의 모서리 주재(chord) + 면마다 X자 사재와 수평재'로 된 상자형 트러스로 짰다.
# 사재·수평재는 다시 두 줄 플랜지와 지그재그 웨브로 된 격자 거더라서 멀리서도 연철 격자가 읽힌다.
# 다리 하나를 bmesh 로 만든 뒤 90°씩 회전한 연결 복제(linked duplicate)로 나머지 셋을 놓았다.
# 1층 아래 네 면의 장식 아치, 아케이드 띠를 두른 1·2층 전망대, 3층 유리 객실과 꼭대기 등탑·안테나를 더했다.
# 리벳, 계단, 경사 엘리베이터, 2차 이하의 잔 격자는 생략했다.
from atelier import *

SHOT = {"azimuth": -70, "elevation": 6, "lens": 60, "light": "day", "background": "#232c3b"}

# ---------------------------------------------------------------- 재질
IRON = mat('Eiffel_Bronze_Paint', '#7a6350', 0.55, 0.3)
IRON_DARK = mat('Iron_Secondary', '#584536', 0.6, 0.3)
STONE = mat('Masonry_Pier', '#9a8f7e', 0.85, 0.0)
DECK = mat('Deck_Planking', '#5a4e44', 0.8, 0.05)
GLAZE = mat('Glazing', '#405463', 0.1, 0.6)
ANT_W = mat('Antenna_White', '#d2cdc3', 0.45, 0.1)
ANT_R = mat('Antenna_Red', '#a43a2e', 0.45, 0.1)
LAMP = mat('Beacon_Brass', '#c69a4c', 0.3, 0.85, emit='#ffcf7a', emit_strength=1.5)


# ---------------------------------------------------------------- 윤곽 곡선
def pchip(xs, ys):
    n = len(xs)
    h = [xs[i + 1] - xs[i] for i in range(n - 1)]
    d = [(ys[i + 1] - ys[i]) / h[i] for i in range(n - 1)]
    m = [0.0] * n
    m[0], m[-1] = d[0], d[-1]
    for i in range(1, n - 1):
        if d[i - 1] * d[i] <= 0:
            m[i] = 0.0
        else:
            w1, w2 = 2 * h[i] + h[i - 1], h[i] + 2 * h[i - 1]
            m[i] = (w1 + w2) / (w1 / d[i - 1] + w2 / d[i])

    def f(x):
        x = min(max(x, xs[0]), xs[-1])
        i = 0
        while i < n - 2 and x > xs[i + 1]:
            i += 1
        t = (x - xs[i]) / h[i]
        t2, t3 = t * t, t * t * t
        return ((2 * t3 - 3 * t2 + 1) * ys[i] + (t3 - 2 * t2 + t) * h[i] * m[i]
                + (-2 * t3 + 3 * t2) * ys[i + 1] + (t3 - t2) * h[i] * m[i + 1])
    return f


# 바깥 반폭(중심→바깥 모서리)과 다리 안쪽 모서리까지의 거리
W = pchip([0, 30, 57.6, 90, 115.7, 150, 195, 240, 276, 300],
          [62.5, 47.5, 36.0, 26.4, 20.8, 15.4, 11.2, 8.4, 6.6, 5.2])
I = pchip([0, 57.6, 115.7, 150, 185, 200], [36.5, 16.8, 7.5, 4.2, 2.8, 2.8])
F1, F2, F3 = 57.6, 115.7, 276.0          # 1·2·3층 바닥 높이
PIER = 4.0                                # 석조 기초 높이
Z_LEG = [PIER, 18, 31, 44, F1, 68, 78, 88, 97, 106.5, F2, 126, 136, 146, 156, 166, 176, 186, 195]
Z_SHAFT = [195, 213, 230, 246, 261, F3]


def chord_size(z):
    return 2.3 - 1.3 * min(z, 195) / 195 - 0.3 * max(z - 195, 0) / 81


# ---------------------------------------------------------------- bmesh 도우미
class MB:
    def __init__(self):
        self.bm = bmesh.new()

    def quad(self, vs):
        try:
            self.bm.faces.new(vs)
        except ValueError:
            pass

    def bar(self, p0, p1, t, n, t2=None):
        """p0→p1 사각 단면 부재. n 은 단면 방향을 잡는 법선 힌트."""
        p0, p1 = Vector(p0), Vector(p1)
        d = p1 - p0
        if d.length < 1e-4:
            return
        d.normalize()
        u = d.cross(Vector(n))
        if u.length < 1e-4:
            u = d.cross(Vector((1, 0, 0)))
        u.normalize()
        v = u.cross(d).normalized()
        a, b = t / 2, (t2 if t2 else t) / 2
        offs = [u * a + v * b, -u * a + v * b, -u * a - v * b, u * a - v * b]
        r0 = [self.bm.verts.new(p0 + o) for o in offs]
        r1 = [self.bm.verts.new(p1 + o) for o in offs]
        for i in range(4):
            j = (i + 1) % 4
            self.quad((r0[i], r0[j], r1[j], r1[i]))
        self.quad(list(reversed(r0)))
        self.quad(r1)

    def girder(self, p0, p1, n, width, t, off=0.0):
        """두 줄 플랜지 + 지그재그 웨브의 평면 격자 거더."""
        p0, p1, n = Vector(p0), Vector(p1), Vector(n).normalized()
        d = p1 - p0
        L = d.length
        w = n.cross(d.normalized()).normalized() * (width / 2)
        s = n * off
        a0, a1, b0, b1 = p0 + w + s, p1 + w + s, p0 - w + s, p1 - w + s
        self.bar(a0, a1, t, n)
        self.bar(b0, b1, t, n)
        k = max(2, round(L / (width * 1.15)))
        pts = [(a0.lerp(a1, i / k) if i % 2 == 0 else b0.lerp(b1, i / k)) for i in range(k + 1)]
        for i in range(k):
            self.bar(pts[i], pts[i + 1], t * 0.6, n)

    def sweep(self, pts, sizes):
        """세로 주재: 각 점에서 수평 정사각 단면을 이어 붙인 연속 관."""
        rings = []
        for (x, y, z), s in zip(pts, sizes):
            h = s / 2
            rings.append([self.bm.verts.new((x + dx * h, y + dy * h, z))
                          for dx, dy in ((1, 1), (-1, 1), (-1, -1), (1, -1))])
        for r0, r1 in zip(rings, rings[1:]):
            for i in range(4):
                j = (i + 1) % 4
                self.quad((r0[i], r0[j], r1[j], r1[i]))
        self.quad(list(reversed(rings[0])))
        self.quad(rings[-1])

    def sq_ring(self, outer, inner, z0, z1):
        """가운데가 뚫린 정사각 고리 판(바닥판, 띠)."""
        def ring(hh, z):
            return [self.bm.verts.new((sx * hh, sy * hh, z)) for sx, sy in ((1, 1), (-1, 1), (-1, -1), (1, -1))]
        ob, ot, ib, it = ring(outer, z0), ring(outer, z1), ring(inner, z0), ring(inner, z1)
        for i in range(4):
            j = (i + 1) % 4
            self.quad((ob[i], ob[j], ot[j], ot[i]))
            self.quad((it[i], it[j], ib[j], ib[i]))
            self.quad((ot[i], ot[j], it[j], it[i]))
            self.quad((ib[i], ib[j], ob[j], ob[i]))

    def block(self, c, size):
        (x, y, z), (sx, sy, sz) = c, size
        self.bar((x, y, z - sz / 2), (x, y, z + sz / 2), sx, (0, 1, 0), sy)

    def finish(self, name, material, smooth_=False):
        bmesh.ops.remove_doubles(self.bm, verts=self.bm.verts, dist=1e-4)
        bmesh.ops.recalc_face_normals(self.bm, faces=self.bm.faces)
        me = bpy.data.meshes.new(name)
        self.bm.to_mesh(me)
        self.bm.free()
        ob = bpy.data.objects.new(name, me)
        link(ob)
        assign(ob, material)
        if smooth_:
            smooth(ob)
        return ob


def frange(a, b, step):
    n = max(1, round((b - a) / step))
    return [a + (b - a) * i / n for i in range(n + 1)]


def face_normal(a0, b0, a1, outward):
    n = (Vector(b0) - Vector(a0)).cross(Vector(a1) - Vector(a0)).normalized()
    return n if n.dot(Vector(outward)) >= 0 else -n


def lattice_face(mb, A, B, zs, outward, hw, dw, t, skip_first_h=False, split=1):
    """두 주재 A(z), B(z) 사이의 면: 마디마다 수평 거더, split 칸으로 나눠 칸마다 X 사재."""
    cols = [(lambda z, f=i / split: Vector(A(z)).lerp(Vector(B(z)), f)) for i in range(split + 1)]
    for k, z in enumerate(zs):
        a, b = A(z), B(z)
        z1 = zs[min(k + 1, len(zs) - 1)]
        n = face_normal(a, b, A(z1) if k + 1 < len(zs) else A(zs[k - 1]), outward)
        if not (k == 0 and skip_first_h):
            mb.girder(a, b, n, hw(z), t, off=-0.05)
        if k + 1 < len(zs):
            for j in range(split):
                p, q = cols[j], cols[j + 1]
                mb.girder(p(z), q(z1), n, dw(z), t, off=0.0)
                mb.girder(q(z), p(z1), n, dw(z), t, off=0.09)
            for j in range(1, split):
                mb.girder(cols[j](z), cols[j](z1), n, dw(z) * 0.8, t, off=0.04)


# ---------------------------------------------------------------- 다리 (하나 만들고 회전 복제)
def build_leg():
    mb = MB()
    P = {
        'WW': lambda z: (W(z), W(z), z),
        'WI': lambda z: (W(z), I(z), z),
        'IW': lambda z: (I(z), W(z), z),
        'II': lambda z: (I(z), I(z), z),
    }
    # 모서리 주재 — 바깥 모서리는 3층까지 이어져 상부 탑신의 모서리가 된다
    for key, top in (('WW', F3), ('WI', 195), ('IW', 195), ('II', 195)):
        zs = frange(PIER, top, 2.5)
        mb.sweep([P[key](z) for z in zs], [chord_size(z) for z in zs])
    hw = lambda z: 0.7 + 0.03 * (W(z) - I(z))
    dw = lambda z: 0.5 + 0.016 * (W(z) - I(z))
    lattice_face(mb, P['WI'], P['WW'], Z_LEG, (1, 0, 0), hw, dw, 0.3, split=2)
    lattice_face(mb, P['IW'], P['WW'], Z_LEG, (0, 1, 0), hw, dw, 0.3, split=2)
    lattice_face(mb, P['II'], P['IW'], Z_LEG, (-1, 0, 0), hw, dw, 0.3, split=2)
    lattice_face(mb, P['II'], P['WI'], Z_LEG, (0, -1, 0), hw, dw, 0.3, split=2)
    # 다리 내부의 수평 가새(마디마다 대각선)
    for z in Z_LEG[1:]:
        mb.bar(P['II'](z), P['WW'](z), 0.3, (0, 0, 1))
    return mb.finish('Leg_NE', IRON)


def build_piers():
    mb = MB()
    for key in ((1, 1), (1, 0), (0, 1), (0, 0)):
        x = W(PIER) if key[0] else I(PIER)
        y = W(PIER) if key[1] else I(PIER)
        mb.block((x, y, 1.2), (8.0, 8.0, 2.4))
        mb.block((x, y, 2.4 + (PIER - 2.4) / 2 + 0.3), (5.0, 5.0, PIER - 2.4 + 0.6))
    return mb.finish('Masonry_Piers_NE', STONE)


# ---------------------------------------------------------------- 상부 탑신 (195m → 3층)
def build_shaft():
    mb = MB()
    corners = [(1, 1), (-1, 1), (-1, -1), (1, -1)]
    for i in range(4):
        sa, sb = corners[i], corners[(i + 1) % 4]
        A = lambda z, s=sa: (s[0] * W(z), s[1] * W(z), z)
        B = lambda z, s=sb: (s[0] * W(z), s[1] * W(z), z)
        out = ((sa[0] + sb[0]) / 2, (sa[1] + sb[1]) / 2, 0)
        lattice_face(mb, A, B, Z_SHAFT, out, lambda z: 1.0, lambda z: 0.85, 0.26, skip_first_h=True, split=2)
    for z in Z_SHAFT[1:-1]:
        mb.bar((W(z), W(z), z), (-W(z), -W(z), z), 0.28, (0, 0, 1))
        mb.bar((-W(z), W(z), z), (W(z), -W(z), z), 0.28, (0, 0, 1))
    return mb.finish('Upper_Shaft', IRON)


def build_lift_column():
    """2층에서 3층까지 탑 중심을 오르는 엘리베이터 기둥."""
    mb = MB()
    h = 1.6
    for sx, sy in ((1, 1), (-1, 1), (-1, -1), (1, -1)):
        mb.sweep([(sx * h, sy * h, F2 - 0.6), (sx * h, sy * h, F3)], [0.45, 0.45])
    zs = frange(F2, F3, 8.0)
    for z in zs:
        mb.sq_ring(h + 0.18, h - 0.18, z - 0.15, z + 0.15)
    for z0, z1 in zip(zs, zs[1:]):
        for (ax, ay), (bx, by), n in (((1, 1), (1, -1), (1, 0, 0)), ((-1, 1), (-1, -1), (-1, 0, 0)),
                                      ((1, 1), (-1, 1), (0, 1, 0)), ((1, -1), (-1, -1), (0, -1, 0))):
            mb.bar((ax * h, ay * h, z0), (bx * h, by * h, z1), 0.18, n)
    return mb.finish('Lift_Column', IRON_DARK)


# ---------------------------------------------------------------- 1층 아래 장식 아치 (앞면 -Y, 회전 복제)
def build_arch():
    mb, web = MB(), MB()
    z0, depth = 12.0, 3.6
    a_in = I(z0) + 0.5
    b_in = 50.6 - depth - z0
    ints, exts = [], []
    N = 72
    for i in range(N + 1):
        ph = math.pi * i / N
        x, z = a_in * math.cos(ph), z0 + b_in * math.sin(ph)
        if abs(x) > I(z) + 0.6:
            continue
        xe, ze = (a_in + depth) * math.cos(ph), z0 + (b_in + depth) * math.sin(ph)
        ints.append((x, -(W(z) - 1.0), z))
        exts.append((xe, -(W(ze) - 1.0), ze))
    n = (0, -1, 0.0)
    for crv in (ints, exts):
        for p, q in zip(crv, crv[1:]):
            mb.bar(p, q, 0.8, n, 1.1)
    for i in range(len(ints) - 1):
        a, b, c, d = (Vector(v) for v in (ints[i], ints[i + 1], exts[i + 1], exts[i]))
        sh = Vector((0, 0.35, 0))
        web.quad([web.bm.verts.new(v + sh) for v in (a, b, c, d)])
        web.quad([web.bm.verts.new(v + sh * 1.6) for v in (d, c, b, a)])
        mb.bar(ints[i], exts[i + 1] if i % 2 == 0 else exts[i], 0.3, n)
        mb.bar(exts[i] if i % 2 == 0 else exts[i + 1], ints[i + 1], 0.3, n)
    for i in range(0, len(ints), 3):
        mb.bar(ints[i], exts[i], 0.35, n)
    return mb.finish('Arch_South', IRON), web.finish('Arch_South_Web', IRON_DARK)


# ---------------------------------------------------------------- 전망대
def arcade_band(mb, D, z0, z1, post_step, t=0.5):
    """전망대 둘레의 아케이드 띠: 위아래 띠장 + 기둥 + 작은 반원 아치."""
    mb.sq_ring(D + t / 2, D - t / 2, z0, z0 + t * 1.4)
    mb.sq_ring(D + t / 2 + 0.05, D - t / 2 - 0.05, z1 - t * 1.6, z1)
    hgt = z1 - z0
    for sx, sy, ax in ((0, -1, 'x'), (0, 1, 'x'), (-1, 0, 'y'), (1, 0, 'y')):
        xs = frange(-D, D, post_step)
        n = (sx, sy, 0)

        def P(u, z):
            return (u, sy * D, z) if ax == 'x' else (sx * D, u, z)
        for u in xs[1:-1]:
            mb.bar(P(u, z0 + t), P(u, z1 - t), t * 0.8, n)
        spring = z0 + hgt * 0.45
        for u0, u1 in zip(xs, xs[1:]):
            r = (u1 - u0) / 2
            c = (u0 + u1) / 2
            arc = [P(c + r * math.cos(math.pi * k / 8), spring + r * 0.9 * math.sin(math.pi * k / 8)) for k in range(9)]
            for p, q in zip(arc, arc[1:]):
                mb.bar(p, q, t * 0.45, n)
            mb.bar(P(c, spring + r * 0.9), P(c, z1 - t), t * 0.35, n)


def railing(mb, D, z, step=2.0, hgt=1.2):
    mb.sq_ring(D + 0.08, D - 0.08, z + hgt - 0.12, z + hgt)
    mb.sq_ring(D + 0.04, D - 0.04, z + hgt * 0.5 - 0.05, z + hgt * 0.5 + 0.05)
    for sx, sy, ax in ((0, -1, 'x'), (0, 1, 'x'), (-1, 0, 'y'), (1, 0, 'y')):
        for u in frange(-D, D, step)[:-1]:
            p = (u, sy * D) if ax == 'x' else (sx * D, u)
            mb.bar((p[0], p[1], z), (p[0], p[1], z + hgt - 0.1), 0.1, (1, 0, 0))


def build_floor(name, D, deck_inner, zf, band_h, post_step, rail_step):
    iron, deck, back = MB(), MB(), MB()
    t = 0.6 if band_h > 5 else 0.45
    arcade_band(iron, D, zf - band_h, zf + 0.6, post_step, t=t)
    back.sq_ring(D - t * 0.55, D - t * 0.55 - 0.25, zf - band_h + 0.2, zf + 0.4)
    railing(iron, D - 0.2, zf + 0.6, rail_step)
    deck.sq_ring(D - 0.25, deck_inner, zf - 0.6, zf + 0.0)
    back.finish(name + '_Frieze_Panel', IRON_DARK)
    return iron.finish(name + '_Gallery', IRON), deck.finish(name + '_Deck', DECK)


def build_pavilion(name, half_x, y0, y1, z0, z1):
    """전망대 위 유리 파빌리온(1층 레스토랑 등) — 앞면(-Y) 한 채."""
    glass_mb, frame = MB(), MB()
    cx, cy, h = 0.0, (y0 + y1) / 2, z1 - z0
    glass_mb.block((cx, cy, z0 + (h - 0.8) / 2), (2 * half_x - 0.4, abs(y1 - y0) - 0.4, h - 0.8))
    frame.block((cx, cy, z1 - 0.4), (2 * half_x + 0.6, abs(y1 - y0) + 0.6, 0.8))
    for u in frange(-half_x, half_x, 2.2):
        for yy in (y0, y1):
            frame.bar((u, yy, z0), (u, yy, z1 - 0.8), 0.22, (0, -1, 0))
    for v in frange(min(y0, y1), max(y0, y1), 2.2)[1:-1]:
        for xx in (-half_x, half_x):
            frame.bar((xx, v, z0), (xx, v, z1 - 0.8), 0.22, (1, 0, 0))
    for yy in (y0, y1):
        frame.bar((-half_x, yy, z0 + h * 0.55), (half_x, yy, z0 + h * 0.55), 0.18, (0, -1, 0))
    return glass_mb.finish(name + '_Glass', GLAZE), frame.finish(name + '_Frame', IRON)


# ---------------------------------------------------------------- 3층과 꼭대기
def build_summit():
    iron, glass_mb, deck, white, red, lamp = MB(), MB(), MB(), MB(), MB(), MB()
    zf = F3
    deck.sq_ring(9.8, 2.2, zf - 1.0, zf)
    iron.sq_ring(9.95, 9.55, zf - 1.6, zf + 0.1)
    # 아래층 유리 객실
    glass_mb.sq_ring(7.6, 2.4, zf, zf + 3.4)
    for sx, sy, ax in ((0, -1, 'x'), (0, 1, 'x'), (-1, 0, 'y'), (1, 0, 'y')):
        for u in frange(-7.75, 7.75, 1.94):
            p = (u, sy * 7.75) if ax == 'x' else (sx * 7.75, u)
            iron.bar((p[0], p[1], zf), (p[0], p[1], zf + 3.4), 0.16, (1, 0, 0))
    iron.sq_ring(8.7, 2.4, zf + 3.4, zf + 4.0)
    railing(iron, 8.5, zf + 4.0, 1.6, 1.3)
    # 위층 사무실(에펠의 방)
    iron.sq_ring(4.6, 2.4, zf + 4.0, zf + 7.2)
    for u in frange(-4.0, 4.0, 2.0):
        for s in (-1, 1):
            glass_mb.block((u, s * 4.65, zf + 5.6), (1.1, 0.12, 1.6))
            glass_mb.block((s * 4.65, u, zf + 5.6), (0.12, 1.1, 1.6))
    deck.sq_ring(5.1, 0.0001, zf + 7.2, zf + 7.7)
    # 등탑을 받치는 네 모서리 리브와 면 아치
    zb, zt = zf + 4.0, 297.0
    for sx, sy in ((1, 1), (-1, 1), (-1, -1), (1, -1)):
        p0 = Vector((sx * 8.0, sy * 8.0, zb))
        c = Vector((sx * 6.8, sy * 6.8, 291.0))
        p1 = Vector((sx * 1.9, sy * 1.9, zt))
        pts = [(1 - t) ** 2 * p0 + 2 * (1 - t) * t * c + t * t * p1 for t in [i / 12 for i in range(13)]]
        for p, q in zip(pts, pts[1:]):
            iron.bar(p, q, 0.55, (sx, -sy, 0))
    for sx, sy, ax in ((0, -1, 'x'), (0, 1, 'x'), (-1, 0, 'y'), (1, 0, 'y')):
        arc = []
        for k in range(17):
            ph = math.pi * k / 16
            u = 7.6 * math.cos(ph)
            z = zf + 7.7 + 7.0 * math.sin(ph)
            off = 7.6 - 1.0 * math.sin(ph)
            arc.append((u, sy * off, z) if ax == 'x' else (sx * off, u, z))
        for p, q in zip(arc, arc[1:]):
            iron.bar(p, q, 0.4, (sx, sy, 0))
    for z, hh in ((289.0, 5.5), (293.5, 3.9)):
        iron.sq_ring(hh + 0.15, hh - 0.15, z - 0.15, z + 0.15)
    # 등탑과 돔
    iron.sq_ring(2.6, 0.0001, zt - 0.3, zt + 0.3)
    bm = lamp.bm
    bmesh.ops.create_cone(bm, cap_ends=True, segments=24, radius1=1.7, radius2=1.7, depth=3.2,
                          matrix=Matrix.Translation((0, 0, zt + 0.3 + 1.6)))
    for k in range(8):
        a = 2 * math.pi * k / 8
        iron.bar((1.78 * math.cos(a), 1.78 * math.sin(a), zt + 0.3), (1.78 * math.cos(a), 1.78 * math.sin(a), zt + 3.5), 0.18, (math.cos(a), math.sin(a), 0))
    prof = [(2.3, zt + 3.5), (2.3, zt + 3.9), (2.0, zt + 4.6), (1.3, zt + 5.3), (0.6, zt + 5.7), (0.0001, zt + 5.8)]
    rings = []
    for i in range(24):
        a = 2 * math.pi * i / 24
        rings.append([iron.bm.verts.new((r * math.cos(a), r * math.sin(a), z)) for r, z in prof])
    for i in range(24):
        r0, r1 = rings[i], rings[(i + 1) % 24]
        for j in range(len(prof) - 1):
            iron.quad((r0[j], r1[j], r1[j + 1], r0[j + 1]))
    # 안테나 (330m 까지)
    z = zt + 5.6
    bands = [(1.1, 5.0, white), (1.0, 3.0, red), (0.9, 3.0, white), (0.75, 3.0, red), (0.65, 3.0, white),
             (0.5, 2.6, red), (0.4, 2.6, white), (0.3, 2.4, red), (0.18, 330.0 - z - 24.6, white)]
    for r, hgt, part in bands:
        bmesh.ops.create_cone(part.bm, cap_ends=True, segments=16, radius1=r, radius2=r * 0.95, depth=hgt,
                              matrix=Matrix.Translation((0, 0, z + hgt / 2)))
        z += hgt
    for zz, rr in ((zt + 10.6, 2.0), (zt + 16.6, 1.5)):
        bmesh.ops.create_cone(white.bm, cap_ends=True, segments=20, radius1=rr, radius2=rr, depth=0.25,
                              matrix=Matrix.Translation((0, 0, zz)))
    return [iron.finish('Summit_Ironwork', IRON), glass_mb.finish('Summit_Cabin_Glass', GLAZE),
            deck.finish('Summit_Deck', DECK), white.finish('Antenna_Mast', ANT_W, True),
            red.finish('Antenna_Red_Bands', ANT_R, True), lamp.finish('Beacon_Lantern', LAMP, True)]


# ---------------------------------------------------------------- 조립
def rotated(ob, k, name):
    return ob if k == 0 else duplicate(ob, rot=(0, 0, k * math.pi / 2), name=name)


leg = build_leg()
piers = build_piers()
for k, nm in enumerate(['NE', 'NW', 'SW', 'SE']):
    rotated(leg, k, 'Leg_' + nm)
    rotated(piers, k, 'Masonry_Piers_' + nm)

arch, arch_web = build_arch()
for k, nm in enumerate(['South', 'East', 'North', 'West']):
    rotated(arch, k, 'Arch_' + nm)
    rotated(arch_web, k, 'Arch_%s_Web' % nm)

build_shaft()
build_lift_column()

D1 = W(F1) + 1.0
build_floor('Floor1', D1, 14.0, F1, 6.6, 3.0, 2.0)
pg, pf = build_pavilion('Floor1_Pavilion_S', 13.0, -(D1 - 1.6), -(D1 - 9.0), F1, F1 + 6.2)
for k, nm in enumerate(['S', 'E', 'N', 'W']):
    rotated(pg, k, 'Floor1_Pavilion_%s_Glass' % nm)
    rotated(pf, k, 'Floor1_Pavilion_%s_Frame' % nm)

D2 = W(F2) + 0.8
build_floor('Floor2', D2, 3.8, F2, 3.8, 2.4, 2.0)
pg2, pf2 = build_pavilion('Floor2_Pavilion_S', 6.0, -(D2 - 1.4), -(D2 - 6.4), F2, F2 + 5.6)
for k, nm in enumerate(['S', 'E', 'N', 'W']):
    rotated(pg2, k, 'Floor2_Pavilion_%s_Glass' % nm)
    rotated(pf2, k, 'Floor2_Pavilion_%s_Frame' % nm)
up = MB()
up.sq_ring(W(F2 + 6.2) + 0.3, 9.0, F2 + 5.6, F2 + 6.2)
up.finish('Floor2_Upper_Deck', DECK)
upr = MB()
railing(upr, W(F2 + 6.2) + 0.1, F2 + 6.2, 2.0)
upr.finish('Floor2_Upper_Railing', IRON)

for ob in build_summit():
    ob.scale = (0.88, 0.88, 1.0)   # 3층 객실 평면을 상부 탑신 폭에 맞춘다
