# 파르테논 신전 (아테네 아크로폴리스, 기원전 447~432년) — 지붕과 조각이 남아 있던 완공 당시 외관을 단순화해 복원한 모델.
# 동쪽 정면이 -Y 방향. 기단 30.88 x 69.5m, 8 x 17 열 도리스식 주주(周柱) 46개, 기둥 높이 10.43m.
# 기둥은 bmesh 로 20개의 홈(flute)과 배흘림(entasis)을 계산해 만든 몸통에 회전체 에키누스와 사각 아바쿠스를 얹어
# 하나의 메시로 합친 뒤, 연결 복제(linked duplicate)로 배치했다. 모서리 기둥 간격은 실제처럼 좁혔다(corner contraction).
# 엔타블라처(아키트레이브·트리글리프 프리즈·코니스)는 사각 고리 메시와 반복 부품으로, 박공·지붕·기와줄·안테픽스는
# 경사각(13.5°)에서 계산한 단면 돌출로 만들었다. 기단과 기둥의 미세한 곡률(곡선 기단, 안쪽 기울기)은 생략했다.
import math
import random
from atelier import *

random.seed(447)

SHOT = {"azimuth": -58, "elevation": 11, "lens": 50, "light": "day", "background": "#1e2836"}

# ── 재질: 펜텔리콘 대리석의 꿀빛 풍화색 몇 단계 ──────────────────────────────
M_COL = mat('Marble_Columns', '#ddcfb4', rough=0.55)
M_ENT = mat('Marble_Entablature', '#d3c2a2', rough=0.6)
M_TRI = mat('Marble_Triglyph', '#ae9a78', rough=0.65)
M_MET = mat('Marble_Metope', '#c9b693', rough=0.6)
M_STEP = mat('Limestone_Steps', '#b4a383', rough=0.7)
M_WALL = mat('Marble_Cella_Wall', '#cdbd9d', rough=0.65)
M_ROOF = mat('Marble_Roof_Tiles', '#b9aa90', rough=0.6)
M_SCUL = mat('Marble_Sculpture', '#e6dcc8', rough=0.5)
M_DOOR = mat('Bronze_Door', '#5b4630', rough=0.45, metal=0.7)
M_CEIL = mat('Marble_Ceiling', '#b9a789', rough=0.7)

# ── 주요 치수 ─────────────────────────────────────────────────────────────
STEP_H = 0.55
Z0 = 3 * STEP_H                    # 스틸로베이트 윗면
SX, SY = 15.44, 34.76              # 스틸로베이트 반폭/반길이
COL_H = 10.43
ZC = Z0 + COL_H                    # 기둥 꼭대기 = 아키트레이브 바닥
ARCH_X, ARCH_Y = 15.36, 34.68      # 아키트레이브 바깥면
IN_X, IN_Y = 13.60, 32.85          # 엔타블라처 고리 안쪽
Z_TAENIA = ZC + 1.23
Z_FRIEZE = ZC + 1.35
FR_X, FR_Y = 15.30, 34.62          # 프리즈 면
Z_CORN = Z_FRIEZE + 1.35
CO_X, CO_Y = 15.95, 35.30          # 코니스 바깥면
Z_TOP = Z_CORN + 0.60
SLOPE = math.tan(math.radians(13.5))
ROOF_T = 0.30


def mesh_from_bm(name, bm, material, loc=(0, 0, 0)):
    me = bpy.data.meshes.new(name)
    bm.to_mesh(me)
    bm.free()
    ob = bpy.data.objects.new(name, me)
    link(ob)
    ob.location = loc
    return assign(ob, material)


def frame(name, ohx, ohy, ihx, ihy, z0, z1, material):
    """속이 빈 사각 고리(엔타블라처 한 켜)."""
    bm = bmesh.new()
    rect = lambda hx, hy, z: [bm.verts.new(p) for p in ((-hx, -hy, z), (hx, -hy, z), (hx, hy, z), (-hx, hy, z))]
    ob0, ob1 = rect(ohx, ohy, z0), rect(ohx, ohy, z1)
    ib0, ib1 = rect(ihx, ihy, z0), rect(ihx, ihy, z1)
    for i in range(4):
        j = (i + 1) % 4
        bm.faces.new((ob0[i], ob0[j], ob1[j], ob1[i]))
        bm.faces.new((ib0[j], ib0[i], ib1[i], ib1[j]))
        bm.faces.new((ob1[i], ob1[j], ib1[j], ib1[i]))
        bm.faces.new((ob0[j], ob0[i], ib0[i], ib0[j]))
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
    return mesh_from_bm(name, bm, material)


def fluted_shaft(name, h, r0, r1, flutes=20, per=5, rings=11, depth=0.055, swell=0.02):
    """홈 20개와 배흘림이 있는 기둥 몸통. 홈과 홈 사이 모서리(arris)는 날카롭게."""
    bm = bmesh.new()
    seg = flutes * per
    grid = []
    for i in range(rings):
        t = i / (rings - 1)
        R = r0 + (r1 - r0) * t + swell * math.sin(math.pi * t)
        row = []
        for j in range(seg):
            a = 2 * math.pi * j / seg
            r = R - depth * (R / r0) * math.sin(math.pi * (j % per) / per)
            row.append(bm.verts.new((r * math.cos(a), r * math.sin(a), h * t)))
        grid.append(row)
    for i in range(rings - 1):
        for j in range(seg):
            k = (j + 1) % seg
            bm.faces.new((grid[i][j], grid[i][k], grid[i + 1][k], grid[i + 1][j]))
    bm.faces.new(list(reversed(grid[0])))
    bm.faces.new(grid[-1])
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
    for f in bm.faces:
        f.smooth = len(f.verts) == 4
    arris = {grid[i][j] for i in range(rings) for j in range(0, seg, per)}
    caps = set(grid[0]) | set(grid[-1])
    for e in bm.edges:
        a, b = e.verts
        if (a in arris and b in arris) or (a in caps and b in caps):
            e.smooth = False
    return mesh_from_bm(name, bm, None)


def doric_column(name, total_h, r0=0.955, r1=0.74):
    shaft_h = total_h - 0.86
    shaft = fluted_shaft(name + '_Shaft', shaft_h, r0, r1)
    prof = [(0, 0), (r1 - 0.01, 0), (r1 + 0.02, 0.03), (r1 - 0.005, 0.05), (r1 + 0.02, 0.08), (r1 - 0.005, 0.10), (r1 + 0.005, 0.12)]
    for k in range(1, 10):
        t = k / 9
        prof.append((r1 + 0.005 + (1.01 - r1 - 0.005) * (1 - (1 - t) ** 2.2), 0.12 + 0.38 * t))
    prof.append((0, 0.5))
    ech = lathe(name + '_Echinus', prof, loc=(0, 0, shaft_h), seg=60)
    aba = box(name + '_Abacus', (2.04, 2.04, 0.36), loc=(0, 0, shaft_h + 0.5), base=True)
    col = join([shaft, ech, aba], name)
    return assign(col, M_COL)


def bake(ob):
    """오브젝트 위치를 메시에 굽고 원점을 (0,0,0)으로 — 복제용 원형을 만들 때."""
    ob.data.transform(Matrix.Translation(ob.location))
    ob.location = (0, 0, 0)
    return ob


def facing(side, t, d, z):
    """정면(-Y) 기준으로 만든 부품을 네 면 중 하나에 놓는 위치/회전."""
    if side == 'E':
        return (t, -d, z), (0, 0, 0)
    if side == 'W':
        return (-t, d, z), (0, 0, math.pi)
    if side == 'N':
        return (d, t, z), (0, 0, math.pi / 2)
    return (-d, -t, z), (0, 0, -math.pi / 2)


# ── 1. 기단(크레피도마) 3단 ───────────────────────────────────────────────
for i in range(3):
    g = (2 - i) * 0.70
    box(f'Crepidoma_Step_{i + 1}', (2 * (SX + g), 2 * (SY + g), STEP_H), loc=(0, 0, i * STEP_H), base=True, material=M_STEP)

# ── 2. 바깥 주주(周柱) 8 x 17 ─────────────────────────────────────────────
def axes(n_inner_gaps, normal=4.296, corner=3.68):
    half = n_inner_gaps * normal / 2
    inner = [-half + k * normal for k in range(n_inner_gaps + 1)]
    return [-half - corner] + inner + [half + corner]


XS = axes(5)                          # 정면 8개
YS = axes(14, corner=3.66)            # 측면 17개
COL = doric_column('Column_Peristyle', COL_H + 0.02)
positions = [(x, y) for x in XS for y in (YS[0], YS[-1])] + [(x, y) for x in (XS[0], XS[-1]) for y in YS[1:-1]]
for i, (x, y) in enumerate(positions):
    ob = COL if i == 0 else duplicate(COL, name=f'Column_Peristyle_{i:02d}')
    ob.location = (x, y, Z0 - 0.02)

# ── 3. 엔타블라처 ────────────────────────────────────────────────────────
frame('Architrave', ARCH_X, ARCH_Y, IN_X, IN_Y, ZC, Z_TAENIA, M_ENT)
frame('Architrave_Taenia', ARCH_X + 0.06, ARCH_Y + 0.06, IN_X, IN_Y, Z_TAENIA, Z_FRIEZE, M_ENT)
frame('Frieze', FR_X, FR_Y, IN_X, IN_Y, Z_FRIEZE, Z_CORN, M_MET)
frame('Cornice_Geison', CO_X, CO_Y, IN_X, IN_Y, Z_CORN, Z_TOP, M_ENT)
box('Ambulatory_Ceiling', (2 * (IN_X + 0.05), 2 * (IN_Y + 0.05), Z_TOP - Z_FRIEZE), loc=(0, 0, Z_FRIEZE), base=True, material=M_CEIL)

# 트리글리프(세로 홈 3줄) — 정면 기준(-Y), 뒷면이 y=0
tg_parts = [box('tg_back', (0.84, 0.05, 1.35), loc=(0, -0.025, 0), base=True)]
for bx in (-0.28, 0.0, 0.28):
    tg_parts.append(box('tg_bar', (0.16, 0.10, 1.28), loc=(bx, -0.05, 0), base=True))
for bx in (-0.40, 0.40):
    tg_parts.append(box('tg_edge', (0.04, 0.10, 1.28), loc=(bx, -0.05, 0), base=True))
tg_parts.append(box('tg_cap', (0.84, 0.10, 0.07), loc=(0, -0.05, 1.28), base=True))
TRIGLYPH = bake(assign(join(tg_parts, 'Triglyph'), M_TRI))

# 레굴라 + 구따(물방울 장식 6개) — 아키트레이브 띠 아래
rg_parts = [box('rg_strip', (0.80, 0.06, 0.08), loc=(0, -0.03, -0.08), base=True)]
for k in range(6):
    rg_parts.append(cylinder('rg_gutta', r=0.045, r2=0.035, h=0.08, loc=(-0.3125 + k * 0.125, -0.035, -0.16), seg=8))
REGULA = bake(assign(join(rg_parts, 'Regula_Guttae'), M_ENT))

# 무툴루스 — 코니스 아랫면의 납작한 판
MUTULE = box('Mutule', (0.78, 0.60, 0.07), loc=(0, -0.30, Z_CORN - 0.07), base=True, material=M_ENT)
bake(MUTULE)

# 메토프 부조 — 두 인물이 맞붙은 장면을 낮은 타원체 몇 개로 암시(변형 3종)
RELIEFS = []
for v in range(3):
    parts = []
    for k, (x, tilt, h) in enumerate(((-0.24, 0.25 - 0.15 * v, 0.40), (0.22, -0.35 + 0.2 * v, 0.36 + 0.03 * v))):
        parts.append(sphere('rf_body', r=1.0, loc=(x, -0.02, 0.55), rot=(0, tilt, 0), scale=(0.15, 0.06, h), seg=12, rings=8))
        hx = x - math.sin(tilt) * (h + 0.08)
        parts.append(sphere('rf_head', r=0.09, loc=(hx, -0.03, 0.55 + h * math.cos(tilt) + 0.06), scale=(1, 0.6, 1.1), seg=10, rings=6))
    parts.append(sphere('rf_limb', r=1.0, loc=(0.0, -0.02, 0.62 - 0.1 * v), rot=(0, 1.1 - 0.5 * v, 0), scale=(0.05, 0.04, 0.32), seg=8, rings=6))
    RELIEFS.append(bake(assign(join(parts, f'Metope_Relief_{v}'), M_SCUL)))

first = {'tg': True, 'rg': True, 'mu': True, 0: True, 1: True, 2: True}


def put(proto, key, name, side, t, d, z):
    loc, rot = facing(side, t, d, z)
    if first[key]:
        first[key] = False
        proto.location, proto.rotation_euler, proto.name = loc, rot, name
        return proto
    return duplicate(proto, loc=loc, rot=rot, name=name)


def triglyph_stations(cols, face_half):
    st = []
    for i, c in enumerate(cols):
        st.append(c)
        if i < len(cols) - 1:
            st.append((c + cols[i + 1]) / 2)
    st[0], st[-1] = -face_half + 0.42, face_half - 0.42      # 모서리 트리글리프는 모서리에 붙인다
    return st


for side, cols, fh, fd, ad, cd in (('E', XS, FR_X, FR_Y, ARCH_Y, CO_Y), ('W', XS, FR_X, FR_Y, ARCH_Y, CO_Y),
                                   ('N', YS, FR_Y, FR_X, ARCH_X, CO_X), ('S', YS, FR_Y, FR_X, ARCH_X, CO_X)):
    st = triglyph_stations(cols, fh)
    for i, t in enumerate(st):
        put(TRIGLYPH, 'tg', f'Triglyph_{side}_{i:02d}', side, t, fd, Z_FRIEZE)
        put(REGULA, 'rg', f'Regula_{side}_{i:02d}', side, t, ad, Z_TAENIA)
    mids = st + [(st[i] + st[i + 1]) / 2 for i in range(len(st) - 1)]
    for i, t in enumerate(sorted(mids)):
        put(MUTULE, 'mu', f'Mutule_{side}_{i:02d}', side, t, fd + 0.10 if t in st else fd, 0)
    for i in range(len(st) - 1):
        v = random.randrange(3)
        put(RELIEFS[v], v, f'Metope_Relief_{side}_{i:02d}', side, (st[i] + st[i + 1]) / 2, fd, Z_FRIEZE)
# 모서리의 빈 틈을 메우는 작은 블록
for sx in (-1, 1):
    for sy in (-1, 1):
        box('Frieze_Corner_Block', (0.10, 0.10, 1.35), loc=(sx * (FR_X + 0.05), sy * (FR_Y + 0.05), Z_FRIEZE), base=True, material=M_TRI)

# ── 4. 박공(페디먼트)과 지붕 ─────────────────────────────────────────────
def zb(x):
    """지붕판 아랫면 높이."""
    return Z_TOP + (CO_X - abs(x)) * SLOPE


EAVE = CO_X + 0.10
APEX = zb(0)
ROOF_L = CO_Y + 0.15
extrude_profile('Pediment_Tympanum_Core', [(-FR_X, Z_TOP), (FR_X, Z_TOP), (0, APEX)], 2 * FR_Y, material=M_MET)
extrude_profile('Roof_Slab', [(-EAVE, zb(EAVE)), (0, APEX), (EAVE, zb(EAVE)), (EAVE, zb(EAVE) + ROOF_T),
                              (0, APEX + ROOF_T), (-EAVE, zb(EAVE) + ROOF_T)], 2 * ROOF_L, material=M_ROOF)
RAKE_D = 0.62
for sy, nm in ((-1, 'East'), (1, 'West')):
    y0, y1 = FR_Y - 0.12, CO_Y + 0.12
    for sx, sn in ((-1, 'L'), (1, 'R')):
        xe = CO_X + 0.06                      # 측면 코니스 면과 겹치지 않게 살짝 내민다
        pts = [(0, APEX - RAKE_D), (sx * xe, zb(xe) - RAKE_D), (sx * xe, zb(xe)), (0, APEX)]
        if sx < 0:
            pts = list(reversed(pts))
        extrude_profile(f'Raking_Cornice_{nm}_{sn}', pts, y1 - y0, loc=(0, sy * (y0 + y1) / 2, 0), material=M_ENT)

# 기와: 덮개기와 줄(임브렉스)과 처마 끝 안테픽스
SL = EAVE / math.cos(math.atan(SLOPE))
ang = math.atan(SLOPE)
pitch = 0.66
n_rows = int(2 * (ROOF_L - 0.2) / pitch)
y_start = -(n_rows - 1) * pitch / 2
tile = box('Roof_Cover_Tile_Row', (SL, 0.18, 0.12), material=M_ROOF)
ant_pts = [(-0.16, 0), (0.16, 0), (0.20, 0.16), (0.12, 0.30), (0, 0.40), (-0.12, 0.30), (-0.20, 0.16)]
ANT = extrude_profile('Antefix', ant_pts, 0.06, material=M_ENT, axis='X')
first_tile, first_ant = True, True
for i in range(n_rows):
    y = y_start + i * pitch
    for sx in (-1, 1):
        cx = sx * (SL / 2) * math.cos(ang) + sx * 0.06 * math.sin(ang)
        cz = APEX + ROOF_T - (SL / 2) * math.sin(ang) + 0.06 * math.cos(ang)
        rot = (0, sx * ang, 0)
        if first_tile:
            tile.location, tile.rotation_euler = (cx, y, cz), rot
            first_tile = False
        else:
            duplicate(tile, loc=(cx, y, cz), rot=rot, name='Roof_Cover_Tile_Row')
        loc = (sx * (EAVE - 0.05), y, zb(EAVE) + ROOF_T - 0.02)
        rz = math.pi / 2 if sx > 0 else -math.pi / 2
        if first_ant:
            ANT.location, ANT.rotation_euler = loc, (0, 0, rz)
            first_ant = False
        else:
            duplicate(ANT, loc=loc, rot=(0, 0, rz), name='Antefix')
box('Roof_Ridge', (0.42, 2 * ROOF_L, 0.16), loc=(0, 0, APEX + ROOF_T - 0.04), base=True, material=M_ROOF)

# ── 5. 내부 셀라(나오스)와 6주식 현관 ──────────────────────────────────────
for i, (hx, hy) in enumerate(((11.5, 29.6), (11.1, 29.2))):
    box(f'Cella_Platform_Step_{i + 1}', (2 * hx, 2 * hy, 0.35), loc=(0, 0, Z0 + 0.35 * i), base=True, material=M_STEP)
ZI = Z0 + 0.70
WX0, WX1 = 9.9, 10.9
for sx in (-1, 1):
    box('Cella_Side_Wall', (WX1 - WX0, 54.0, ZC - ZI), loc=(sx * (WX0 + WX1) / 2, 0, ZI), base=True, material=M_WALL)
DOOR_W, DOOR_H = 4.9, 10.0 * 0.95
for sy, nm in ((-1, 'East'), (1, 'West')):
    yc = sy * 24.0
    for sx in (-1, 1):
        box(f'Cella_End_Wall_{nm}', (WX0 - DOOR_W / 2, 1.0, ZC - ZI), loc=(sx * (WX0 + DOOR_W / 2) / 2, yc, ZI), base=True, material=M_WALL)
    box(f'Cella_Door_Lintel_{nm}', (DOOR_W, 1.0, ZC - ZI - DOOR_H), loc=(0, yc, ZI + DOOR_H), base=True, material=M_WALL)
    box(f'Cella_Door_{nm}', (DOOR_W, 0.3, DOOR_H), loc=(0, yc, ZI), base=True, material=M_DOOR)
frame('Cella_Frieze', WX1 + 0.05, 28.95, WX0 - 0.05, 27.05, ZC, Z_FRIEZE, M_ENT)

# 주랑 천장의 가로보(기둥 축마다)
beam = box('Ambulatory_Ceiling_Beam', (IN_X - WX1 + 0.1, 0.5, 0.42), loc=((IN_X + WX1) / 2, YS[1], Z_FRIEZE - 0.42), base=True, material=M_ENT)
for x in (-1, 1):
    for y in YS[1:-1]:
        if (x, y) != (1, YS[1]):
            duplicate(beam, loc=(x * (IN_X + WX1) / 2, y, Z_FRIEZE - 0.42), name='Ambulatory_Ceiling_Beam')
for sy in (-1, 1):
    for xx in XS[1:-1]:
        duplicate(beam, loc=(xx, sy * (IN_Y + 28.95) / 2, Z_FRIEZE - 0.42), rot=(0, 0, math.pi / 2),
                  scale=((IN_Y - 28.95 + 0.1) / (IN_X - WX1 + 0.1), 1, 1), name='Ambulatory_Ceiling_Beam')

PORCH = doric_column('Column_Porch', ZC - ZI + 0.02, r0=0.86, r1=0.67)
PX = [-9.5, -5.7, -1.9, 1.9, 5.7, 9.5]
k = 0
for y in (-28.0, 28.0):
    for x in PX:
        ob = PORCH if k == 0 else duplicate(PORCH, name=f'Column_Porch_{k:02d}')
        ob.location = (x, y, ZI - 0.02)
        k += 1

# ── 6. 박공 조각군과 아크로테리온 ─────────────────────────────────────────
def figure_proto(name, kind):
    """단순화한 조각상: 서 있는 인물(standing), 앉은 인물(seated), 기댄 인물(reclining). 높이 1m 기준."""
    if kind in ('standing', 'striding'):
        lean = 0.0 if kind == 'standing' else 0.12
        parts = [cylinder('fg', r=0.21, r2=0.15, h=0.52, seg=14),                      # 옷자락
                 cylinder('fg', r=0.15, r2=0.13, h=0.30, loc=(lean * 0.5, 0, 0.5), seg=12),
                 sphere('fg', r=1.0, loc=(lean, 0, 0.79), scale=(0.21, 0.11, 0.08), seg=12, rings=6),
                 sphere('fg', r=0.075, loc=(lean, 0, 0.9), scale=(1, 1, 1.15), seg=12, rings=8),
                 sphere('fg', r=1.0, loc=(-0.19, 0, 0.62), rot=(0, 0.15, 0), scale=(0.05, 0.05, 0.2), seg=8, rings=6)]
        if kind == 'striding':
            parts.append(sphere('fg', r=1.0, loc=(0.26 + lean, 0, 0.95), rot=(0, 0.6, 0), scale=(0.045, 0.045, 0.22), seg=8, rings=6))
        else:
            parts.append(sphere('fg', r=1.0, loc=(0.19, 0, 0.62), rot=(0, -0.15, 0), scale=(0.05, 0.05, 0.2), seg=8, rings=6))
    elif kind == 'seated':
        parts = [box('fg', (0.36, 0.30, 0.36), loc=(0, 0, 0), base=True),
                 cylinder('fg', r=0.15, r2=0.11, h=0.5, loc=(0, 0.02, 0.34), seg=12),
                 sphere('fg', r=0.085, loc=(0, 0.02, 0.92), seg=12, rings=8),
                 sphere('fg', r=1.0, loc=(0, -0.12, 0.33), scale=(0.17, 0.2, 0.07), seg=10, rings=6)]
    else:
        parts = [sphere('fg', r=1.0, loc=(0, 0, 0.16), scale=(0.62, 0.17, 0.16), seg=14, rings=8),
                 sphere('fg', r=1.0, loc=(-0.45, 0, 0.36), rot=(0, -0.5, 0), scale=(0.17, 0.14, 0.24), seg=12, rings=6),
                 sphere('fg', r=0.085, loc=(-0.52, 0, 0.62), seg=10, rings=6)]
    return bake(assign(join(parts, name), M_SCUL))


FIG = {k: figure_proto(f'Pediment_Figure_{k}', k) for k in ('standing', 'striding', 'seated', 'reclining')}
used = set()
for sy, nm in ((-1, 'East'), (1, 'West')):
    x = -14.2
    i = 0
    while x < 14.3:
        room = (zb(x) - RAKE_D) - Z_TOP          # 박공 안쪽 높이
        if room < 0.7:
            x += 0.6
            continue
        if room < 1.6:
            kind, h, step = 'reclining', min(room * 0.85, 1.2), 1.15
        elif room < 2.4:
            kind, h, step = 'seated', room * 0.82, 0.62
        else:
            kind, h, step = random.choice(('standing', 'striding')), min(room * 0.86, 3.0), 0.5
        if abs(x) < 0.5:
            kind, h = 'standing', 3.15             # 가운데 주신(主神)
        s = h * random.uniform(0.94, 1.02)
        flip = 1 if x < 0 else -1
        loc = (x, sy * (FR_Y + 0.34), Z_TOP)
        rot = (0, 0, (0 if sy < 0 else math.pi) + random.uniform(-0.25, 0.25))
        sc = (s * flip if kind in ('reclining', 'striding') else s, s * 1.3, s)
        proto = FIG[kind]
        if kind not in used:
            used.add(kind)
            proto.location, proto.rotation_euler, proto.scale = loc, rot, sc
            proto.name = f'Pediment_Figure_{nm}_{i:02d}'
        else:
            duplicate(proto, loc=loc, rot=rot, scale=sc, name=f'Pediment_Figure_{nm}_{i:02d}')
        x += step * s
        i += 1

# 아크로테리온: 꼭대기의 꽃 장식(팔메트)과 모서리 받침
palm = [(-0.55, 0)]
for k in range(13):
    a = math.pi * (1 - k / 12)
    rr = 1.0 if k % 2 == 0 else 0.78
    palm.append((rr * 0.85 * math.cos(a), 0.55 + rr * 1.05 * math.sin(a)))
palm.append((0.55, 0))
ACRO = extrude_profile('Acroterion_Palmette', palm, 0.22, material=M_SCUL)
bake(ACRO)
for sy, nm in ((-1, 'East'), (1, 'West')):
    yy = sy * (CO_Y - 0.15)
    base = box(f'Acroterion_Base_{nm}', (1.1, 0.6, 0.45), loc=(0, yy, APEX + ROOF_T - 0.12), base=True, material=M_ENT)
    ob = ACRO if nm == 'East' else duplicate(ACRO, name='Acroterion_Palmette')
    ob.location = (0, yy, APEX + ROOF_T + 0.33)
    ob.scale = (1.3, 1.0, 1.3)
    for sx in (-1, 1):
        box(f'Acroterion_Corner_Base_{nm}', (0.8, 0.8, 0.35), loc=(sx * (CO_X - 0.4), yy - sy * 0.25, zb(CO_X) + ROOF_T - 0.05), base=True, material=M_ENT)
        duplicate(ACRO, loc=(sx * (CO_X - 0.4), yy - sy * 0.25, zb(CO_X) + ROOF_T + 0.30), scale=(0.75, 1.0, 0.75), name='Acroterion_Corner_Palmette')
