본문으로 건너뛰기
3D 아틀리에
파르테논 신전 3D 모형

끌어서 회전, 휠·두 손가락으로 확대

이 모형에 대하여

파르테논은 기원전 447년부터 432년 사이 아테네 아크로폴리스에 세워진 아테나 여신의 신전이다. 페리클레스 시대에 건축가 익티노스와 칼리크라테스가 설계하고 조각가 페이디아스가 조각 작업을 이끌었으며, 건물 전체가 펜텔리콘산 대리석으로 지어졌다.

세 단의 기단 위에 정면 8개, 측면 17개의 도리스식 기둥이 둘러서 있고, 그 위로 트리글리프와 메토프가 번갈아 놓인 프리즈와 낮은 박공이 얹혀 있다. 기둥의 배흘림, 모서리 기둥 간격의 축소, 기단의 미세한 곡률 같은 시각 보정 기법이 쓰인 것으로 잘 알려져 있다.

신전은 이후 교회와 모스크로 쓰였고, 1687년 베네치아군의 포격으로 내부에 보관된 화약이 폭발하면서 지붕과 중앙부가 크게 무너졌다. 이 모델은 지붕과 박공 조각, 아크로테리온이 남아 있던 완공 당시의 외관을 단순화해 복원한 모습이다.

만든 방법

기둥 몸통은 bmesh 로 20개의 홈과 배흘림을 계산해 만들고, 홈 사이 모서리만 날카로운 모서리로 지정했다. 에키누스는 회전체(lathe), 아바쿠스는 상자로 만들어 한 메시로 합친 뒤 연결 복제로 58개 기둥(외부 46개, 현관 12개)을 배치했고, 모서리 기둥 간격은 실제처럼 좁혔다. 아키트레이브·프리즈·코니스는 속이 빈 사각 고리 메시를 층층이 쌓았고, 트리글리프·레굴라·무툴루스·메토프 부조는 기둥 축과 그 중간점에서 계산한 위치에 반복 배치했다. 박공과 지붕, 기와줄, 안테픽스는 13.5° 경사에서 계산한 단면 돌출과 배열로 만들었다. 박공 조각과 메토프 부조는 원작을 재현한 것이 아니라 박공 높이에 맞춰 크기를 정한 단순한 인물 형태로 암시했으며, 기단의 곡률과 기둥의 안쪽 기울기, 채색 흔적은 생략했다.

build.py

이 파일 하나를 Blender 에서 실행하면 위의 모형이 만들어집니다. 396줄.

# 파르테논 신전 (아테네 아크로폴리스, 기원전 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()
나머지 348줄 펼치기
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')

다른 모형