# 타지마할 (인도 아그라, 1632~1653) — Blender Python 코드만으로 만든 모델.
# 붉은 사암 테라스 위 흰 대리석 기단(약 95m 사방), 모서리를 깎은 팔각 본당(약 57m),
# 네 면의 큰 이완(pishtaq) 아치와 2단 벽감, 드럼 위 양파형 돔, 네 개의 차트리, 기단 모서리의 첨탑 네 개.
# 돔·첨탑·차트리 지붕은 단면 곡선을 Z축으로 돌린 회전체(lathe)로, 아치 벽감은 뾰족 아치 단면 프리즘을
# 불리언으로 파냈다. 아치 테두리의 검은 대리석 상감 띠는 얇은 판으로 표현했고, 같은 부품(첨탑·차트리·
# 소첨탑)은 메시를 공유하는 복제본으로 배치했다. 미터 단위 실물 크기 기준이며, 기단 바닥에서 첨두까지 약 78m로
# 실제(약 73m)보다 조금 높게 잡혔다.
from atelier import *
from mathutils import Matrix, Vector
import math

SHOT = {"azimuth": -72, "elevation": 8, "lens": 50, "light": "day", "background": "#26324a"}

# ── 재질 ────────────────────────────────────────────────────────────
M_MARBLE = mat('Makrana marble', '#ebe4d6', 0.32)
M_MARBLE_SHADE = mat('Marble recess', '#d8cfbf', 0.45)
M_INLAY = mat('Black marble inlay', '#3b3631', 0.3)
M_PIETRA = mat('Pietra dura inlay', '#8c6a4c', 0.4)
M_GOLD = mat('Gilded bronze finial', '#c8a04a', 0.28, 1.0)
M_SAND = mat('Red sandstone', '#8c4636', 0.8)
M_JALI = mat('Marble jali screen', '#6d6257', 0.6)
M_DOOR = mat('Dark doorway', '#2e2a27', 0.7)
MATS = [M_MARBLE, M_INLAY, M_GOLD, M_JALI, M_DOOR, M_MARBLE_SHADE, M_PIETRA, M_SAND]
MI = {m: i for i, m in enumerate(MATS)}

# ── 치수 (m) ───────────────────────────────────────────────────────
TER_H = 1.2                 # 붉은 사암 테라스
Z0 = TER_H + 6.7            # 대리석 기단 윗면
PL = 95.0                   # 기단 한 변
S = 28.5                    # 본당 반폭
C = 10.5                    # 모서리 깎임
WALL_H = 26.0               # 본당 벽 높이(기단 위)
ROOF = WALL_H + 0.15        # 처마 띠 윗면 = 지붕면
PISH_W, PISH_PJ, PISH_H = 21.0, 1.2, 31.5
CHAMF_D = (2 * S - C) / math.sqrt(2)


def frame(cx, cy, nx, ny, z=0.0):
    """면 좌표계: u=면을 따라(정면에서 볼 때 오른쪽), d=안쪽, z=위."""
    tx, ty = -ny, nx
    return Matrix(((tx, -nx, 0, cx), (ty, -ny, 0, cy), (0, 0, 1, z), (0, 0, 0, 1)))


def arch_profile(w, z0, hs, k=0.65, n=9, u=0.0):
    """뾰족(이심) 아치 단면 (u, z) 점 목록, 반시계."""
    h = w / 2
    R = k * w
    cx = R - h
    a_top = math.acos(cx / R)
    pts = [(u - h, z0), (u + h, z0)]
    for i in range(n + 1):                       # 오른쪽 호: 0 → a_top (중심은 왼쪽)
        a = a_top * i / n
        pts.append((u - cx + R * math.cos(a), hs + R * math.sin(a)))
    for i in range(1, n + 1):                    # 왼쪽 호: 꼭짓점 → 스프링
        a = math.pi - a_top + (a_top) * i / n
        pts.append((u + cx + R * math.cos(a), hs + R * math.sin(a)))
    # 꼭짓점 중복 제거
    out = []
    for p in pts:
        if not out or (abs(out[-1][0] - p[0]) > 1e-6 or abs(out[-1][1] - p[1]) > 1e-6):
            out.append(p)
    return out


class Part:
    """여러 재질 면을 한 bmesh 에 모아 하나의 메시 오브젝트로 만든다."""

    def __init__(self, name):
        self.name = name
        self.bm = bmesh.new()

    def _tag(self, faces, m, sm):
        for f in faces:
            f.material_index = MI[m]
            f.smooth = sm

    def box(self, M, lo, hi, m=M_MARBLE):
        c = [(a + b) / 2 for a, b in zip(lo, hi)]
        s = [b - a for a, b in zip(lo, hi)]
        T = M @ Matrix.Translation(c) @ Matrix.Diagonal((*s, 1))
        r = bmesh.ops.create_cube(self.bm, size=1.0, matrix=T)
        self._tag({f for v in r['verts'] for f in v.link_faces}, m, False)

    def prism(self, pts, z0, z1, M=Matrix(), m=M_MARBLE):
        b = [self.bm.verts.new(M @ Vector((x, y, z0))) for x, y in pts]
        t = [self.bm.verts.new(M @ Vector((x, y, z1))) for x, y in pts]
        fs = [self.bm.faces.new(list(reversed(b))), self.bm.faces.new(t)]
        n = len(pts)
        for i in range(n):
            fs.append(self.bm.faces.new((b[i], b[(i + 1) % n], t[(i + 1) % n], t[i])))
        self._tag(fs, m, False)

    def lathe(self, prof, M=Matrix(), seg=32, m=M_MARBLE, smooth=True, rot0=0.0):
        rings = []
        for r, z in prof:
            if r < 1e-6:
                rings.append([self.bm.verts.new(M @ Vector((0, 0, z)))] * seg)
            else:
                rings.append([self.bm.verts.new(M @ Vector((r * math.cos(rot0 + 2 * math.pi * i / seg),
                                                            r * math.sin(rot0 + 2 * math.pi * i / seg), z)))
                              for i in range(seg)])
        fs = []
        for j in range(len(prof) - 1):
            a, b = rings[j], rings[j + 1]
            for i in range(seg):
                i1 = (i + 1) % seg
                q = [a[i], a[i1], b[i1], b[i]]
                q2 = []
                for v in q:
                    if v not in q2:
                        q2.append(v)
                if len(q2) >= 3:
                    fs.append(self.bm.faces.new(q2))
        self._tag(fs, m, smooth)

    def cyl(self, M, r, z0, z1, seg=16, m=M_MARBLE, r2=None):
        self.lathe([(0, z0), (r, z0), (r if r2 is None else r2, z1), (0, z1)], M, seg, m, smooth=True)

    def arch(self, M, w, z0, hs, d0, d1, k=0.65, u=0.0, m=M_MARBLE):
        prof = arch_profile(w, z0, hs, k, u=u)
        f_ = [self.bm.verts.new(M @ Vector((x, d0, z))) for x, z in prof]
        b_ = [self.bm.verts.new(M @ Vector((x, d1, z))) for x, z in prof]
        fs = [self.bm.faces.new(f_), self.bm.faces.new(list(reversed(b_)))]
        n = len(prof)
        for i in range(n):
            fs.append(self.bm.faces.new((f_[i], b_[i], b_[(i + 1) % n], f_[(i + 1) % n])))
        bmesh.ops.recalc_face_normals(self.bm, faces=fs)
        self._tag(fs, m, False)

    def arch_band(self, M, w, z0, hs, b, d0, d1, k=0.65, u=0.0, m=M_INLAY):
        """아치 입구를 두르는 U자 띠(폭 b) — 상감 테두리."""
        po = arch_profile(w + 2 * b, z0, hs, k * w / (w + 2 * b) + 0.5 * (1 - w / (w + 2 * b)), u=u)
        pi_ = arch_profile(w + 0.06, z0, hs, (k * w + 0.03) / (w + 0.06), u=u)
        po, pi_ = po[1:] + po[:1], pi_[1:] + pi_[:1]          # 오른쪽 밑 → 호 → 왼쪽 밑
        V = lambda p, d: self.bm.verts.new(M @ Vector((p[0], d, p[1])))
        rows = [[V(a, d0), V(c, d0), V(c, d1), V(a, d1)] for a, c in zip(po, pi_)]
        fs = []
        for r0, r1 in zip(rows[:-1], rows[1:]):
            for i in range(4):
                fs.append(self.bm.faces.new((r0[i], r0[(i + 1) % 4], r1[(i + 1) % 4], r1[i])))
        fs.append(self.bm.faces.new(rows[0]))
        fs.append(self.bm.faces.new(list(reversed(rows[-1]))))
        bmesh.ops.recalc_face_normals(self.bm, faces=fs)
        self._tag(fs, m, False)

    def build(self, loc=(0, 0, 0)):
        for e in self.bm.edges:                    # 30° 넘는 모서리는 각지게
            if len(e.link_faces) == 2 and e.calc_face_angle(0) > math.radians(30):
                e.smooth = False
        me = bpy.data.meshes.new(self.name)
        self.bm.to_mesh(me)
        self.bm.free()
        used = sorted({p.material_index for p in me.polygons})
        for m in MATS:
            me.materials.append(m)
        ob = bpy.data.objects.new(self.name, me)
        link(ob)
        ob.location = loc
        return ob


ONION = [(0.86, 0.0), (0.93, 0.05), (0.975, 0.11), (0.997, 0.18), (1.0, 0.25), (0.99, 0.32), (0.96, 0.40),
         (0.905, 0.48), (0.83, 0.56), (0.73, 0.64), (0.61, 0.71), (0.48, 0.775), (0.35, 0.835), (0.23, 0.885),
         (0.14, 0.925), (0.085, 0.955), (0.05, 0.98), (0.0, 1.0)]


def onion(h, rmax, rbase=None):
    """양파형 돔 단면 (r, z): 아래 40% 에서 가장 부풀고 위로 오목하게 뾰족해진다."""
    return [(0.0, 0.0)] + [(r * rmax, z * h) for r, z in ONION]


# ── 붉은 사암 테라스와 대리석 기단 ──────────────────────────────────────
base = Part('Plinth_and_Terrace')
base.box(Matrix(), (-57, -57, 0), (57, 57, TER_H), M_SAND)
base.box(Matrix(), (-58, -58, -0.01), (58, 58, 0.35), M_SAND)                      # 테라스 하단 턱
base.box(Matrix(), (-PL / 2, -PL / 2, TER_H - 0.05), (PL / 2, PL / 2, Z0 - 0.35), M_MARBLE)
base.box(Matrix(), (-PL / 2 - 0.35, -PL / 2 - 0.35, Z0 - 0.6), (PL / 2 + 0.35, PL / 2 + 0.35, Z0), M_MARBLE)
for i in range(-10, 11):                                      # 기단 윗면 대리석 포장 줄눈
    g = i * 4.5
    base.box(Matrix(), (g - 0.09, -PL / 2 + 0.1, Z0 - 0.05), (g + 0.09, PL / 2 - 0.1, Z0 + 0.025), M_MARBLE_SHADE)
    base.box(Matrix(), (-PL / 2 + 0.1, g - 0.09, Z0 - 0.05), (PL / 2 - 0.1, g + 0.09, Z0 + 0.03), M_MARBLE_SHADE)
plinth = base.build()

# 기단 측면의 블라인드 아케이드(불리언으로 파냄)
pc = Part('cut_plinth_arcade')
N_ARC = 17
for side in range(4):
    a = side * math.pi / 2
    nx, ny = math.sin(a) * 1, -math.cos(a)
    M = frame(nx * PL / 2, ny * PL / 2, nx, ny, TER_H)
    step = PL / N_ARC
    for i in range(N_ARC):
        u = -PL / 2 + step * (i + 0.5)
        if abs(u) > PL / 2 - step:   # 첨탑 받침 자리 비움
            continue
        pc.arch(M, 3.4, 0.9, 3.4, -1.0, 0.55, u=u)
cut_pl = pc.build()
boolean(plinth, cut_pl)
plinth.modifiers[-1].use_self = True
apply_all(plinth)
bpy.data.objects.remove(cut_pl, do_unlink=True)

# ── 본당: 팔각 몸체 + 이완 돌출부, 아치 벽감을 파냄 ────────────────────────
oct_pts = [(S - C, -S), (S, -(S - C)), (S, S - C), (S - C, S), (-(S - C), S), (-S, S - C), (-S, -(S - C)), (-(S - C), -S)]
body_p = Part('Mausoleum_Body')
body_p.prism(oct_pts, -0.05, WALL_H)
body = body_p.build(loc=(0, 0, Z0))

pish = Part('cut_pishtaq_union')
cut = Part('cut_alcoves')
FACES = []   # (frame matrix, kind)
for side in range(4):
    a = side * math.pi / 2
    nx, ny = math.sin(a), -math.cos(a)
    FACES.append((frame(nx * S, ny * S, nx, ny), 'main'))
for side in range(4):
    a = side * math.pi / 2 + math.pi / 4
    nx, ny = math.sin(a), -math.cos(a)
    FACES.append((frame(nx * CHAMF_D, ny * CHAMF_D, nx, ny), 'chamfer'))

for M, kind in FACES:
    if kind == 'main':
        pish.box(M, (-PISH_W / 2, -PISH_PJ, -0.05), (PISH_W / 2, 3.0, PISH_H))
        cut.arch(M, 13.6, -0.6, 15.6, -PISH_PJ - 1.0, 4.2)                         # 큰 이완 아치
        cut.arch(M, 5.0, -0.6, 5.6, 4.0, 5.4)                                      # 안쪽 출입문
        cut.arch(M, 5.0, 10.2, 14.6, 4.0, 5.0)                                     # 안쪽 위층 창
        for uu in (-14.25, 14.25):                                                 # 양옆 2단 벽감
            cut.arch(M, 5.0, 1.0, 8.0, -1.0, 2.2, u=uu)
            cut.arch(M, 5.0, 13.6, 20.2, -1.0, 2.2, u=uu)
    else:
        cut.arch(M, 8.6, 1.0, 7.4, -1.0, 3.0, k=0.62)
        cut.arch(M, 8.6, 13.6, 19.4, -1.0, 3.0, k=0.62)

pish_ob = pish.build(loc=(0, 0, Z0))
cut_ob = cut.build(loc=(0, 0, Z0))
boolean(body, pish_ob, 'UNION')
boolean(body, cut_ob, 'DIFFERENCE')
body.modifiers[-1].use_self = True
apply_all(body)
bpy.data.objects.remove(pish_ob, do_unlink=True)
bpy.data.objects.remove(cut_ob, do_unlink=True)

# ── 상감 띠, 처마, 난간, 창살, 문 ───────────────────────────────────────
trim = Part('Mausoleum_Trim')
P = 0.08   # 상감 띠 돌출
for M, kind in FACES:
    if kind == 'main':
        f = -PISH_PJ
        # 이완 아치를 두르는 사각 캘리그래피 띠
        ox, zt, bw = 8.4, 26.6, 0.7
        trim.box(M, (-ox, f - P, 0.0), (-ox + bw, f + 0.05, zt), M_INLAY)
        trim.box(M, (ox - bw, f - P, 0.0), (ox, f + 0.05, zt), M_INLAY)
        trim.box(M, (-ox + bw, f - P, zt - bw), (ox - bw, f + 0.05, zt), M_INLAY)
        for sx in (-1, 1):                                                    # 스팬드럴 피에트라 두라 꽃무늬
            R45 = M @ Matrix.Translation((sx * 6.55, f - 0.04, 23.6)) @ Matrix.Rotation(math.radians(45), 4, 'Y')
            trim.box(R45, (-0.6, -0.04, -0.6), (0.6, 0.09, 0.6), M_PIETRA)
            trim.box(R45, (-0.3, -0.07, -0.3), (0.3, 0.0, 0.3), M_INLAY)
        # 이완 바깥 테두리(밝은 띠)
        trim.box(M, (-PISH_W / 2, f - P / 2, PISH_H - 1.0), (PISH_W / 2, f + 0.05, PISH_H - 0.6), M_INLAY)
        # 이완 위 난간
        trim.box(M, (-PISH_W / 2 - 0.1, f - 0.15, PISH_H - 0.1), (PISH_W / 2 + 0.1, f + 3.0, PISH_H + 0.9))
        trim.arch_band(M, 13.6, 0.0, 15.6, 0.45, f - P, f + 0.05)
        trim.arch_band(M, 5.0, 0.0, 5.6, 0.25, 4.0 - P, 4.05, m=M_PIETRA)
        trim.arch_band(M, 5.0, 10.2, 14.6, 0.25, 4.0 - P, 4.05, m=M_PIETRA)
        # 안쪽 문, 창살
        trim.arch(M, 4.6, -0.05, 5.6, 5.25, 5.5, m=M_DOOR)
        trim.arch(M, 4.6, 10.4, 14.6, 4.85, 5.1, m=M_JALI)
        for uu in (-14.25, 14.25):
            for z0, z1 in ((0.5, 13.0), (13.1, 25.0)):
                for x0, x1 in ((uu - 3.2, uu - 2.9), (uu + 2.9, uu + 3.2)):
                    trim.box(M, (x0, -P, z0), (x1, 0.05, z1), M_INLAY)
                trim.box(M, (uu - 2.9, -P, z1 - 0.3), (uu + 2.9, 0.05, z1), M_INLAY)
            trim.arch_band(M, 5.0, 1.0, 8.0, 0.22, -P, 0.05, u=uu)
            trim.arch_band(M, 5.0, 13.6, 20.2, 0.22, -P, 0.05, u=uu)
            trim.arch(M, 4.0, 1.0, 8.0, 2.05, 2.3, u=uu, m=M_JALI)
            trim.arch(M, 4.0, 13.6, 20.2, 2.05, 2.3, u=uu, m=M_JALI)
    else:
        for z0, z1 in ((0.5, 13.0), (13.1, 25.0)):
            trim.box(M, (-5.2, -P, z0), (-4.9, 0.05, z1), M_INLAY)
            trim.box(M, (4.9, -P, z0), (5.2, 0.05, z1), M_INLAY)
            trim.box(M, (-4.9, -P, z1 - 0.3), (4.9, 0.05, z1), M_INLAY)
        trim.arch_band(M, 8.6, 1.0, 7.4, 0.3, -P, 0.05, k=0.62)
        trim.arch_band(M, 8.6, 13.6, 19.4, 0.3, -P, 0.05, k=0.62)
        trim.arch(M, 7.6, 1.0, 7.4, 2.85, 3.1, k=0.62, m=M_JALI)
        trim.arch(M, 7.6, 13.6, 19.4, 2.85, 3.1, k=0.62, m=M_JALI)

# 처마 띠(지붕판)와 난간
out_pts = [(x * 1.012, y * 1.012) for x, y in oct_pts]
trim.prism(out_pts, WALL_H - 0.75, ROOF)
for i in range(8):
    x0, y0 = oct_pts[i]
    x1, y1 = oct_pts[(i + 1) % 8]
    L = math.hypot(x1 - x0, y1 - y0)
    nx, ny = (y1 - y0) / L, -(x1 - x0) / L
    M = frame((x0 + x1) / 2, (y0 + y1) / 2, nx, ny)
    trim.box(M, (-L / 2, -0.25, ROOF - 0.05), (L / 2, 0.5, ROOF + 1.1))
trim_ob = trim.build(loc=(0, 0, Z0))

# ── 소첨탑(굴다스타): 팔각 모서리와 이완 양옆 ─────────────────────────────
pin = Part('Guldasta_Pinnacle')
pin.cyl(Matrix(), 0.8, 0.0, 1.0, 16)
pin.cyl(Matrix(), 0.65, 1.0, 29.0, 16)
pin.lathe([(0, 29.0), (0.95, 29.0), (0.95, 29.35), (0.6, 29.5), (0.6, 30.6), (0.9, 30.6), (0.9, 30.85), (0, 30.85)], seg=16)
pin.lathe([(0, 30.8), (0.6, 30.8), (0.75, 31.3), (0.6, 31.9), (0.25, 32.4), (0, 32.7)], seg=16)
pin.cyl(Matrix(), 0.08, 32.5, 33.6, 8, M_GOLD, r2=0.02)
pin_ob = pin.build(loc=(0, 0, Z0))
pins = []
for i, (x, y) in enumerate(oct_pts):
    pins.append(pin_ob if i == 0 else duplicate(pin_ob, loc=(x, y, Z0)))
pin_ob.location = (oct_pts[0][0], oct_pts[0][1], Z0)
for M, kind in FACES:
    if kind != 'main':
        continue
    for uu in (-PISH_W / 2, PISH_W / 2):
        p = M @ Vector((uu, -PISH_PJ, 0))
        duplicate(pin_ob, loc=(p.x, p.y, Z0 + 0.0), scale=(1, 1, (PISH_H + 3.5) / 31.0))

# ── 중앙 드럼과 양파형 돔, 금빛 첨두 ─────────────────────────────────────
dome = Part('Main_Dome')
zr = ROOF
DR = 11.6                    # 드럼 반경
dome.lathe([(0, zr - 0.2), (13.2, zr - 0.2), (13.2, zr + 1.4), (12.5, zr + 1.4), (12.5, zr + 1.9),
            (DR, zr + 1.9), (DR, zr + 10.6), (DR + 0.5, zr + 10.7), (DR + 0.5, zr + 11.5), (0, zr + 11.5)], seg=72)
for k in range(2):
    dome.lathe([(DR - 0.02, zr + 2.6 + k * 7.4), (DR + 0.12, zr + 2.6 + k * 7.4), (DR + 0.12, zr + 2.95 + k * 7.4), (DR - 0.02, zr + 2.95 + k * 7.4)],
               seg=72, m=M_INLAY)
ZD = zr + 11.4
DH = 24.5
dome.lathe([(r, ZD + z) for r, z in onion(DH, 13.3)], seg=80)
# 연꽃 왕관
dome.lathe([(0, ZD + DH * 0.86), (3.6, ZD + DH * 0.86), (3.9, ZD + DH * 0.88), (2.6, ZD + DH * 0.925), (1.3, ZD + DH * 0.975), (0, ZD + DH * 0.985)], seg=32)
N_DR = 20
for i in range(N_DR):                                         # 드럼의 블라인드 아치 테두리
    a = 2 * math.pi * (i + 0.5) / N_DR
    M = frame(DR * math.cos(a), DR * math.sin(a), math.cos(a), math.sin(a), zr)
    dome.arch_band(M, 2.1, 3.4, 7.2, 0.16, -0.1, 0.12)
dome_ob = dome.build(loc=(0, 0, Z0))

fin = Part('Dome_Finial')
fz = ZD + DH - 0.4
fin.lathe([(0, fz), (1.0, fz), (1.0, fz + 0.4), (0.55, fz + 0.8), (1.25, fz + 1.8), (0.55, fz + 2.8), (0.95, fz + 3.5),
           (0.4, fz + 4.2), (0.75, fz + 4.9), (0.3, fz + 5.6), (0.28, fz + 7.6), (0.14, fz + 9.0), (0, fz + 9.3)], seg=24, m=M_GOLD)
fin_ob = fin.build(loc=(0, 0, Z0))

# ── 차트리(작은 정자) 네 개 ────────────────────────────────────────────
def chhatri(name, s=1.0, cols=8):
    ch = Part(name)
    ch.lathe([(0, -0.2 * s), (4.6 * s, -0.2 * s), (4.6 * s, 2.0 * s), (0, 2.0 * s)], seg=8, smooth=False, rot0=math.pi / 8)
    ch.lathe([(0, 1.9 * s), (4.85 * s, 1.9 * s), (4.85 * s, 2.25 * s), (0, 2.25 * s)], seg=8, smooth=False, rot0=math.pi / 8)
    for i in range(cols):
        a = 2 * math.pi * i / cols
        M = Matrix.Translation((3.75 * s * math.cos(a), 3.75 * s * math.sin(a), 0))
        ch.lathe([(0, 2.2 * s), (0.42 * s, 2.2 * s), (0.42 * s, 2.7 * s), (0.3 * s, 2.9 * s), (0.3 * s, 6.1 * s),
                  (0.42 * s, 6.3 * s), (0.42 * s, 6.6 * s), (0, 6.6 * s)], M, seg=10)
    ch.lathe([(0, 6.5 * s), (4.25 * s, 6.5 * s), (4.25 * s, 7.4 * s), (0, 7.4 * s)], seg=8, smooth=False, rot0=math.pi / 8)
    ch.lathe([(0, 7.3 * s), (5.3 * s, 7.3 * s), (5.3 * s, 7.55 * s), (3.9 * s, 8.1 * s), (0, 8.1 * s)], seg=8, smooth=False, rot0=math.pi / 8)
    ch.lathe([(0, 8.0 * s), (3.2 * s, 8.0 * s), (3.2 * s, 8.9 * s), (0, 8.9 * s)], seg=32)
    ch.lathe([(r * s * 0.3, 8.85 * s + z * s * 0.3) for r, z in onion(15.0, 12.0)], seg=32)
    ch.lathe([(0, 13.0 * s), (0.45 * s, 13.0 * s), (0.25 * s, 13.4 * s), (0.4 * s, 13.8 * s), (0.12 * s, 14.4 * s),
              (0.05 * s, 15.6 * s), (0, 15.8 * s)], seg=12, m=M_GOLD)
    return ch


ch_ob = chhatri('Chhatri_Kiosk').build()
RC = 22.5 / math.sqrt(2)
for i, (sx, sy) in enumerate(((1, 1), (-1, 1), (-1, -1), (1, -1))):
    loc = (sx * RC, sy * RC, Z0 + ROOF)
    if i == 0:
        ch_ob.location = loc
    else:
        duplicate(ch_ob, loc=loc)

# ── 첨탑 네 개 ───────────────────────────────────────────────────────
mn = Part('Minaret')
mn.lathe([(0, -0.05), (4.3, -0.05), (4.3, 1.4), (4.0, 1.6), (0, 1.6)], seg=8, smooth=False, rot0=math.pi / 8)


def balcony(z, r):
    return [(r, z), (r + 0.35, z + 0.5), (r + 0.95, z + 1.1), (r + 1.2, z + 1.25), (r + 1.2, z + 1.55),
            (r + 1.05, z + 1.55), (r + 1.05, z + 2.6), (r + 0.85, z + 2.6), (r + 0.85, z + 1.7), (r - 0.1, z + 1.7)]


shaft = [(0, 1.5), (2.7, 1.5)]
segs = [(1.5, 11.5, 2.7, 2.55), (13.2, 22.0, 2.45, 2.32), (23.7, 31.0, 2.22, 2.1)]
for zi, (z0, z1, r0, r1) in enumerate(segs):
    shaft += [(r0, z0), (r1, z1)]
    shaft += balcony(z1, r1)
shaft += [(0, 32.7)]
mn.lathe(shaft, seg=32)
for z0, z1, r0, r1 in segs:                                   # 첨탑 상감 고리
    for t in (0.2, 0.55, 0.9):
        z = z0 + (z1 - z0) * t
        r = r0 + (r1 - r0) * t + 0.03
        mn.lathe([(r, z), (r, z + 0.25)], seg=32, m=M_INLAY)
# 꼭대기 차트리
s = 0.62
TOPZ = 32.6
for i in range(8):
    a = 2 * math.pi * i / 8
    M = Matrix.Translation((2.0 * math.cos(a), 2.0 * math.sin(a), TOPZ))
    mn.lathe([(0, 0), (0.28, 0), (0.22, 0.3), (0.22, 3.4), (0.3, 3.6), (0, 3.6)], M, seg=8)
mn.lathe([(0, TOPZ + 3.5), (2.45, TOPZ + 3.5), (2.45, TOPZ + 4.0), (0, TOPZ + 4.0)], seg=8, smooth=False, rot0=math.pi / 8)
mn.lathe([(0, TOPZ + 3.95), (3.1, TOPZ + 3.95), (3.1, TOPZ + 4.15), (2.2, TOPZ + 4.6), (0, TOPZ + 4.6)], seg=16, smooth=False)
mn.lathe([(r * 0.19, TOPZ + 4.5 + z * 0.19) for r, z in onion(15.0, 12.0)], seg=32)
mn.lathe([(0, TOPZ + 7.1), (0.3, TOPZ + 7.1), (0.15, TOPZ + 7.5), (0.25, TOPZ + 7.8), (0.05, TOPZ + 9.0), (0, TOPZ + 9.1)], seg=10, m=M_GOLD)
mn_ob = mn.build()
MP = PL / 2 - 4.4
for i, (sx, sy) in enumerate(((1, 1), (-1, 1), (-1, -1), (1, -1))):
    loc = (sx * MP, sy * MP, Z0)
    if i == 0:
        mn_ob.location = loc
        mn_ob.name = 'Minaret_NE'
    else:
        duplicate(mn_ob, loc=loc, name=('Minaret_NE', 'Minaret_NW', 'Minaret_SW', 'Minaret_SE')[i])
