# 현대 포니 (1975) — 주지아로가 그린 작고 각진 패스트백을 bpy 코드로만 만든 모델.
# 차체 하부는 길이 방향 단면(모서리를 둥글린 다각형)을 이어 붙인 로프트 메시이고,
# 둥근 휠아치는 원통 불리언으로 파냈다. 캐빈은 앞유리·지붕·길게 누운 뒷유리로 된
# 절두체에 베벨을 주었고, 옆 유리는 캐빈 옆면 위 다각형을 필러 선으로 잘라 얹었다.
# 둥근 헤드램프와 가로 살 그릴, 가는 크롬 범퍼, 펜더 미러, 가로형 테일램프는
# 박스·회전체·관(curve) 조합이며 바퀴 네 개는 메시를 공유하는 링크 복제본이다.
# 단위는 미터, 앞이 -Y. 전장 3.97 · 전폭 1.56 · 전고 1.36 · 휠베이스 2.34.
from atelier import *

SHOT = {"azimuth": -38, "elevation": 13, "lens": 55, "light": "studio", "background": "#2f3a47"}

# ── 재질 ───────────────────────────────────────────────
PAINT = mat("Paint_MustardYellow", "#d09a22", rough=0.32, metal=0.15, coat=1.0)
CHROME = mat("Chrome", "#d4d8dc", rough=0.12, metal=1.0)
TRIM = mat("Trim_BlackRubber", "#1c1d20", rough=0.6)
GLASS = mat("Glass_Tinted", "#1d2a30", rough=0.04, metal=0.2, coat=1.0)
LENS = mat("Headlamp_Lens", "#d5dde0", rough=0.05, metal=0.5)
RED = mat("Taillamp_Red", "#9a1418", rough=0.2)
AMBER = mat("Lamp_Amber", "#d47a10", rough=0.2)
TIRE = mat("Tire_Rubber", "#252527", rough=0.9)
STEEL = mat("Wheel_SteelSilver", "#9da2a8", rough=0.35, metal=0.8)
DARK = mat("Underbody_Dark", "#141517", rough=0.9)
PLATE = mat("Plate_White", "#e2e0d6", rough=0.5)

# ── 주요 치수 ───────────────────────────────────────────
Y_FRONT, Y_REAR = -1.985, 1.985
AXLE_F, AXLE_R = -1.255, 1.085
WHEEL_R = 0.289
HW = 0.779
BELT = 0.865
ROOF = 1.36


def set_sharp(ob, deg=35):
    for p in ob.data.polygons:
        p.use_smooth = True
    ob.data.set_sharp_from_angle(angle=math.radians(deg))
    return ob


def steps(a, b, n):
    return [a + (b - a) * i / n for i in range(n + 1)]


# ── 1. 하부 차체: 단면 로프트 ─────────────────────────────
# (y, 윗면 높이 zt, 반폭 hw, 바닥 zb)
STATIONS = [
    (-1.985, 0.730, 0.735, 0.300),
    (-1.979, 0.752, 0.758, 0.288),
    (-1.962, 0.764, 0.771, 0.278),
    (-1.925, 0.772, 0.777, 0.270),
    (-1.800, 0.784, 0.779, 0.258),
    (-1.400, 0.808, 0.779, 0.235),
    (-1.000, 0.832, 0.779, 0.220),
    (-0.620, 0.852, 0.779, 0.212),
    (0.000, 0.862, 0.779, 0.212),
    (0.800, 0.872, 0.779, 0.212),
    (1.300, 0.882, 0.779, 0.222),
    (1.700, 0.888, 0.776, 0.240),
    (1.880, 0.886, 0.768, 0.258),
    (1.950, 0.878, 0.756, 0.272),
    (1.973, 0.866, 0.742, 0.282),
    (1.985, 0.850, 0.722, 0.292),
]


def refine(st, sub=3):
    out = []
    for a, b in zip(st, st[1:]):
        for i in range(sub):
            t = i / sub
            out.append(tuple(a[k] + (b[k] - a[k]) * t for k in range(4)))
    out.append(st[-1])
    return out


def section(zt, hw, zb):
    """오른쪽 반 단면 (x, z) — 아래 중앙에서 위 중앙까지."""
    h = zt - zb
    return [
        (0.0, zb),
        (hw - 0.09, zb),
        (hw - 0.035, zb + 0.03),
        (hw - 0.012, zb + 0.11),
        (hw, zb + 0.30 * h),
        (hw, zb + 0.66 * h),          # 허리 캐릭터 라인
        (hw - 0.008, zb + 0.70 * h),
        (hw - 0.014, zt - 0.06),
        (hw - 0.032, zt - 0.015),
        (hw - 0.070, zt),
        (hw * 0.5, zt + 0.010),
        (0.0, zt + 0.014),
    ]


def build_lower_body():
    bm = bmesh.new()
    rings = []
    for y, zt, hw, zb in refine(STATIONS):
        half = section(zt, hw, zb)
        full = half + [(-x, z) for x, z in reversed(half[1:-1])]
        rings.append([bm.verts.new((x, y, z)) for x, z in full])
    n = len(rings[0])
    for a, b in zip(rings, rings[1:]):
        for i in range(n):
            bm.faces.new((a[i], a[(i + 1) % n], b[(i + 1) % n], b[i]))
    bm.faces.new(rings[0])
    bm.faces.new(list(reversed(rings[-1])))
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
    me = bpy.data.meshes.new("Body_Lower")
    bm.to_mesh(me)
    bm.free()
    ob = bpy.data.objects.new("Body_Lower", me)
    link(ob)
    assign(ob, PAINT)
    ob.data.materials.append(DARK)
    for ax in (AXLE_F, AXLE_R):
        for s in (1, -1):
            c = cylinder(f"Cutter_Arch_{'F' if ax < 0 else 'R'}{'R' if s > 0 else 'L'}",
                         r=0.345, h=0.34, loc=(s * (HW - 0.12), ax, WHEEL_R + 0.03),
                         rot=(0, math.pi / 2, 0), base=False, seg=48, material=DARK)
            m = ob.modifiers.new("Arch", 'BOOLEAN')
            m.object = c
            m.solver = 'EXACT'
            m.material_mode = 'TRANSFER'
            c.hide_render = True
            c.hide_viewport = True
            c['atelier_cutter'] = True
    apply_all(ob)
    set_sharp(ob, 40)
    return ob


body = build_lower_body()


def station_at(y):
    y = max(STATIONS[0][0], min(STATIONS[-1][0], y))
    for a, b in zip(STATIONS, STATIONS[1:]):
        if a[0] <= y <= b[0]:
            t = (y - a[0]) / (b[0] - a[0])
            return [a[i] + (b[i] - a[i]) * t for i in (1, 2, 3)]


def surf_x(y, z):
    """오른쪽 옆면에서 (y, z) 의 x."""
    zt, hw, zb = station_at(y)
    pts = section(zt, hw, zb)[1:10]
    for (x0, z0), (x1, z1) in zip(pts, pts[1:]):
        if z0 <= z <= z1:
            return x0 + (x1 - x0) * (z - z0) / (z1 - z0)
    return hw


def surf_z(x, y):
    """윗면에서 (x, y) 의 z."""
    zt, hw, zb = station_at(y)
    ax = abs(x)
    if ax <= hw * 0.5:
        return zt + 0.014 - 0.004 * ax / (hw * 0.5)
    return zt + 0.010 * (hw - 0.070 - ax) / (hw * 0.5 - 0.070)


# ── 2. 캐빈: 앞유리 · 지붕 · 길게 누운 패스트백 뒷유리 ─────
CAB_Y0, CAB_Y1 = -0.66, 1.66      # 벨트라인 높이에서 앞유리/뒷유리 밑단
ROOF_Y0, ROOF_Y1 = -0.10, 0.70    # 지붕 앞/뒤 끝
CAB_HW, ROOF_HW = 0.738, 0.600
CAB_Z0 = 0.80                     # 차체 속에 묻히는 밑면


def side_x(z):
    return CAB_HW - (z - BELT) * (CAB_HW - ROOF_HW) / (ROOF - BELT)


def a_line(z):
    return CAB_Y0 + (z - BELT) * (ROOF_Y0 - CAB_Y0) / (ROOF - BELT)


def c_line(z):
    return CAB_Y1 - (z - BELT) * (CAB_Y1 - ROOF_Y1) / (ROOF - BELT)


def build_cabin():
    bm = bmesh.new()
    zb = CAB_Z0
    xb = side_x(zb)
    B = [bm.verts.new(v) for v in ((-xb, a_line(zb), zb), (xb, a_line(zb), zb),
                                    (xb, c_line(zb), zb), (-xb, c_line(zb), zb))]
    T = [bm.verts.new(v) for v in ((-ROOF_HW, ROOF_Y0, ROOF), (ROOF_HW, ROOF_Y0, ROOF),
                                    (ROOF_HW, ROOF_Y1, ROOF), (-ROOF_HW, ROOF_Y1, ROOF))]
    bm.faces.new(list(reversed(B)))
    bm.faces.new((B[0], B[1], T[1], T[0]))
    bm.faces.new((B[1], B[2], T[2], T[1]))
    bm.faces.new((B[2], B[3], T[3], T[2]))
    bm.faces.new((B[3], B[0], T[0], T[3]))
    bm.faces.new(T)
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
    me = bpy.data.meshes.new("Cabin")
    bm.to_mesh(me)
    bm.free()
    ob = bpy.data.objects.new("Cabin_Greenhouse", me)
    link(ob)
    assign(ob, PAINT)
    bevel(ob, width=0.04, segments=4, angle=15)
    apply_all(ob)
    set_sharp(ob, 50)
    return ob


cabin = build_cabin()


# ── 3. 바퀴: 155SR13 타이어 + 강철 휠과 크롬 캡 ────────────
def build_wheel():
    tw = 0.078
    prof = [(0.165, -tw + 0.012), (0.190, -tw), (0.255, -tw - 0.004), (0.278, -tw + 0.012),
            (WHEEL_R, -tw + 0.035), (WHEEL_R, tw - 0.035), (0.278, tw - 0.012), (0.255, tw + 0.004),
            (0.190, tw), (0.165, tw - 0.012)]
    tire = lathe("Tire", prof, material=TIRE, seg=96)
    rim = [(0.0, 0.010), (0.11, 0.012), (0.13, 0.018), (0.150, 0.020), (0.168, 0.028),
           (0.172, 0.022), (0.172, -0.03), (0.0, -0.03)]
    disc = lathe("Wheel_Disc", rim, material=STEEL, seg=96)
    cap_prof = [(0.0, 0.050), (0.04, 0.048), (0.075, 0.040), (0.095, 0.026), (0.102, 0.012), (0.102, 0.0)]
    cap = lathe("Hubcap", cap_prof, material=CHROME, seg=64)
    parts = [tire, disc, cap]
    for i in range(6):
        a = 2 * math.pi * i / 6 + math.pi / 6
        s = box(f"Wheel_Vent_{i}", (0.03, 0.022, 0.006),
                loc=(0.130 * math.cos(a), 0.130 * math.sin(a), 0.0175), rot=(0, 0, a), material=DARK)
        parts.append(s)
    w = join(parts, "Wheel_FR")
    for p in w.data.polygons:
        p.use_smooth = True
    w.data.set_sharp_from_angle(angle=math.radians(40))
    return w


wheel = build_wheel()
TRACK_X = 0.648
wheel.rotation_euler = (0, math.pi / 2, 0)
wheel.location = (TRACK_X, AXLE_F, WHEEL_R)
duplicate(wheel, loc=(TRACK_X, AXLE_R, WHEEL_R), name="Wheel_RR")
duplicate(wheel, loc=(-TRACK_X, AXLE_F, WHEEL_R), rot=(0, -math.pi / 2, 0), name="Wheel_FL")
duplicate(wheel, loc=(-TRACK_X, AXLE_R, WHEEL_R), rot=(0, -math.pi / 2, 0), name="Wheel_RL")

# 바퀴 사이로 비치는 하부(어두운 판) — 바퀴 안쪽에서 차체 바닥에 붙는다
box("Underbody_Floor", (1.10, 3.30, 0.06), loc=(0, -0.05, 0.215), material=DARK)


# ── 4. 유리: 캐빈 면 위에 얇은 판 ────────────────────────
def _slab_obj(name, bm, material):
    me = bpy.data.meshes.new(name)
    bm.to_mesh(me)
    bm.free()
    ob = bpy.data.objects.new(name, me)
    link(ob)
    return assign(ob, material)


def slab(name, pts, normal, material, off=0.004, th=0.006):
    n = Vector(normal).normalized()
    bm = bmesh.new()
    lo = [bm.verts.new(Vector(p) + n * off) for p in pts]
    hi = [bm.verts.new(Vector(p) + n * (off + th)) for p in pts]
    bm.faces.new(list(reversed(lo)))
    bm.faces.new(hi)
    k = len(pts)
    for i in range(k):
        bm.faces.new((lo[i], lo[(i + 1) % k], hi[(i + 1) % k], hi[i]))
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
    return _slab_obj(name, bm, material)


def clip(poly, f):
    """(y,z) 다각형을 f(y,z) >= 0 인 쪽만 남긴다(서덜랜드-호지먼)."""
    out = []
    for i in range(len(poly)):
        p, q = poly[i], poly[(i + 1) % len(poly)]
        fp, fq = f(*p), f(*q)
        if fp >= 0:
            out.append(p)
        if (fp >= 0) != (fq >= 0):
            t = fp / (fp - fq)
            out.append((p[0] + (q[0] - p[0]) * t, p[1] + (q[1] - p[1]) * t))
    return out


def window(y0, y1, z0, z1, fa, fc):
    """사각 창을 A 필러(앞)·C 필러(뒤) 선에서 각각 fa, fc 만큼 안쪽으로 자른다."""
    poly = [(y0, z0), (y1, z0), (y1, z1), (y0, z1)]
    poly = clip(poly, lambda y, z: y - (a_line(z) + fa))
    poly = clip(poly, lambda y, z: (c_line(z) - fc) - y)
    return poly


def side_pt(s, y, z):
    return (s * side_x(z), y, z)


SIDE_N = (ROOF - BELT, 0, CAB_HW - ROOF_HW)
WZ0, WZ1 = 0.905, 1.315
B_PILLAR = (0.10, 0.17)
for s, tag in ((1, "R"), (-1, "L")):
    nrm = (s * SIDE_N[0], 0, SIDE_N[2])
    frame = window(-1.0, 2.0, WZ0 - 0.015, WZ1 + 0.015, 0.06, 0.07)
    slab(f"Window_Frame_{tag}", [side_pt(s, y, z) for y, z in frame], nrm, TRIM, off=0.001, th=0.003)
    wins = {
        "Front": window(-1.0, B_PILLAR[0], WZ0, WZ1, 0.08, 0.09),
        "Rear": window(B_PILLAR[1], 0.80, WZ0, WZ1, 0.08, 0.09),
        "Quarter": window(0.86, 2.0, WZ0, WZ1, 0.08, 0.16),
    }
    for k, poly in wins.items():
        slab(f"Glass_Side_{k}_{tag}", [side_pt(s, y, z) for y, z in poly], nrm, GLASS, off=0.003, th=0.004)
    # 앞문 삼각 환기창(벤트 윈도) 칸막이: 앞유리 위 세로 크롬 막대
    vy0, vy1 = -0.30, -0.03
    vb = [Vector(side_pt(s, vy0, WZ0)) + Vector(nrm).normalized() * 0.009,
          Vector(side_pt(s, vy1, WZ1 - 0.005)) + Vector(nrm).normalized() * 0.009]
    curve_tube(f"Vent_Window_Divider_{tag}", [tuple(p) for p in vb], radius=0.007, material=CHROME,
               resolution=2, bevel_res=2)
    # 창틀 크롬 테두리(위쪽 지붕선을 따라)
    zt = WZ1 + 0.02
    yz = [(a_line(WZ0) + 0.055, WZ0), (a_line(zt) + 0.055, zt), (c_line(zt) - 0.065, zt), (c_line(WZ0) - 0.065, WZ0)]
    pts = [Vector(side_pt(s, y, z)) + Vector(nrm).normalized() * 0.006 for y, z in yz]
    curve_tube(f"Window_Chrome_{tag}", [tuple(p) for p in pts], radius=0.006, material=CHROME,
               resolution=2, bevel_res=2)


def screen(name, yb, yt, sign, zb_, zt_, inset=0.07):
    def y_at(z):
        return yb + (z - BELT) * (yt - yb) / (ROOF - BELT)

    def hw_at(z):
        return side_x(z) - inset
    pts = [(-hw_at(zb_), y_at(zb_), zb_), (hw_at(zb_), y_at(zb_), zb_),
           (hw_at(zt_), y_at(zt_), zt_), (-hw_at(zt_), y_at(zt_), zt_)]
    dy, dz = (yt - yb), (ROOF - BELT)
    nrm = (0, -dz, dy) if sign > 0 else (0, dz, -dy)
    slab(name, pts, nrm, GLASS, off=0.003, th=0.004)


screen("Glass_Windshield", CAB_Y0, ROOF_Y0, 1, BELT + 0.02, ROOF - 0.04)
screen("Glass_Rear", CAB_Y1, ROOF_Y1, -1, 0.935, ROOF - 0.045, inset=0.09)


# ── 5. 패널 분할선 ─────────────────────────────────────
GROOVE = 0.0045


def groove(name, pts):
    return curve_tube(name, pts, radius=GROOVE, material=DARK, resolution=2, bevel_res=1)


def side_line(name, yz, s=1):
    return groove(name, [(s * surf_x(y, z), y, z) for y, z in yz])


def top_line(name, xy):
    return groove(name, [(x, y, surf_z(x, y)) for x, y in xy])


top_line("Line_Hood_Rear", [(x, -0.72) for x in steps(-0.70, 0.70, 16)])
top_line("Line_Hood_Front", [(x, -1.93) for x in steps(-0.70, 0.70, 16)])
top_line("Line_Trunk_Front", [(x, 1.70) for x in steps(-0.66, 0.66, 14)])
for s, tag in ((1, "R"), (-1, "L")):
    top_line(f"Line_Hood_Side_{tag}", [(s * 0.70, y) for y in steps(-1.93, -0.72, 16)])
    top_line(f"Line_Trunk_Side_{tag}", [(s * 0.66, y) for y in steps(1.70, 1.94, 4)])
    side_line(f"Line_Door_Front_{tag}", [(-0.66, z) for z in steps(0.27, 0.86, 10)], s)
    side_line(f"Line_Door_B_{tag}", [(0.13, z) for z in steps(0.24, 0.86, 10)], s)
    # 뒷문 뒤끝은 세로로 내려오다 휠아치를 따라 돈다
    ARC_R = 0.372
    a0 = math.degrees(math.acos((0.86 - AXLE_R) / ARC_R))
    arc = [(0.86, 0.86)]
    for a in steps(a0, 178, 10):
        r = math.radians(a)
        arc.append((AXLE_R + ARC_R * math.cos(r), WHEEL_R + 0.03 + ARC_R * math.sin(r)))
    arc.append((arc[-1][0], 0.27))
    side_line(f"Line_Door_Rear_{tag}", arc, s)


# ── 6. 가는 크롬 범퍼(앞뒤 같은 모양) ─────────────────────
def bumper_outline():
    outer = [(0.800, -1.76), (0.800, -1.90), (0.775, -1.990), (0.690, -2.030), (0.0, -2.040)]
    inner = [(0.0, -1.982), (0.660, -1.975), (0.740, -1.955), (0.768, -1.90), (0.768, -1.76)]
    right_outer = outer
    left_outer = [(-x, y) for x, y in reversed(outer[:-1])]
    left_inner = [(-x, y) for x, y in reversed(inner[1:])]
    right_inner = inner
    return right_outer + left_outer + left_inner + right_inner


def build_bumper(name, z0):
    bar = prism(name + "_Chrome", bumper_outline(), 0.10, loc=(0, 0, z0), material=CHROME)
    bevel(bar, width=0.014, segments=3, angle=25)
    apply_all(bar)
    strip = prism(name + "_Rubber", [(x * 1.004, y - (0.006 if y < -1.95 else 0)) for x, y in bumper_outline()],
                  0.026, loc=(0, 0, z0 + 0.038), material=TRIM)
    b = join([bar, strip], name)
    set_sharp(b, 40)
    return b


BUMP_Z = 0.37
front_bumper = build_bumper("Bumper_Front", BUMP_Z)
rear_bumper = duplicate(front_bumper, rot=(0, 0, math.pi), name="Bumper_Rear")
# 범퍼 오버라이더(세로 고무 블록)
for s, tag in ((1, "R"), (-1, "L")):
    for yy, rz, nm in ((-2.045, 0, "Front"), (2.045, math.pi, "Rear")):
        ov = box(f"Overrider_{nm}_{tag}", (0.05, 0.05, 0.15), loc=(s * 0.42, yy, BUMP_Z + 0.05), material=TRIM)
        bevel(ov, 0.015, 3)

# ── 7. 앞모습: 가로 살 그릴 · 둥근 헤드램프 · 방향지시등 ─────
YF = Y_FRONT
GZ = 0.615
box("Grille_Back", (1.30, 0.03, 0.175), loc=(0, YF - 0.004, GZ), material=DARK)
for i, z in enumerate(steps(GZ - 0.066, GZ + 0.066, 6)):
    box(f"Grille_Bar_{i}", (0.85, 0.014, 0.008), loc=(0, YF - 0.020, z), material=CHROME)
for i, (sz, lc) in enumerate((((1.32, 0.02, 0.012), (0, YF - 0.016, GZ + 0.092)),
                              ((1.32, 0.02, 0.012), (0, YF - 0.016, GZ - 0.092)))):
    box(f"Grille_Frame_{i}", sz, loc=lc, material=CHROME)
for s, tag in ((1, "R"), (-1, "L")):
    hx = s * 0.545
    ring = cylinder(f"Headlamp_Ring_{tag}", r=0.094, h=0.03, loc=(hx, YF - 0.012, GZ),
                    rot=(math.pi / 2, 0, 0), base=False, material=CHROME, seg=40)
    lens = lathe(f"Headlamp_Lens_{tag}", [(0.0, 0.022), (0.04, 0.019), (0.07, 0.012), (0.084, 0.0)],
                 loc=(hx, YF - 0.026, GZ), rot=(math.pi / 2, 0, 0), material=LENS, seg=40)
    box(f"Grille_Divider_{tag}", (0.012, 0.022, 0.18), loc=(s * 0.432, YF - 0.016, GZ), material=CHROME)
    tl = box(f"Turn_Signal_{tag}", (0.15, 0.03, 0.06), loc=(s * 0.56, YF - 0.002, 0.505), material=AMBER)
    bevel(tl, 0.008, 2)
    sm = box(f"Side_Marker_{tag}", (0.02, 0.07, 0.035),
             loc=(s * (surf_x(-1.80, 0.58) + 0.002), -1.80, 0.58), material=AMBER)
    bevel(sm, 0.005, 2)
# 옛 현대 'HD' 타원 엠블럼
emb = torus("Emblem_Hyundai_HD", R=0.05, r=0.006, loc=(0, YF - 0.032, GZ),
            rot=(math.pi / 2, 0, 0), material=CHROME, seg=40, rseg=8)
emb.scale = (1.0, 0.7, 1.0)
box("Emblem_Back", (0.09, 0.006, 0.055), loc=(0, YF - 0.029, GZ), material=TRIM)
hd = text3d("Emblem_Letters", "HD", size=0.045, depth=0.002, loc=(0, YF - 0.033, GZ - 0.016),
            rot=(math.pi / 2, 0, 0), material=CHROME)
box("Plate_Front", (0.32, 0.012, 0.11), loc=(0, -2.046, BUMP_Z + 0.03), material=PLATE)

# ── 8. 뒷모습: 가로 테일램프 · 번호판 · PONY 배지 ────────────
YR = Y_REAR
TZ = 0.69
for s, tag in ((1, "R"), (-1, "L")):
    rim = box(f"Taillamp_Housing_{tag}", (0.40, 0.022, 0.175), loc=(s * 0.52, YR + 0.006, TZ), material=CHROME)
    bevel(rim, 0.005, 2)
    for k, (x0, x1, m) in enumerate(((0.57, 0.71, RED), (0.44, 0.57, AMBER), (0.33, 0.44, LENS))):
        box(f"Taillamp_{tag}{k}", (x1 - x0 - 0.008, 0.012, 0.155), loc=(s * (x0 + x1) / 2, YR + 0.018, TZ),
            material=m)
        for j, dz in enumerate((-0.045, -0.015, 0.015, 0.045)):
            box(f"Taillamp_Rib_{tag}{k}{j}", (x1 - x0 - 0.01, 0.004, 0.004),
                loc=(s * (x0 + x1) / 2, YR + 0.025, TZ + dz), material=CHROME if m is LENS else m)
box("Rear_Panel_Trim", (0.64, 0.012, 0.012), loc=(0, YR + 0.004, TZ + 0.08), material=CHROME)
box("Plate_Recess", (0.38, 0.012, 0.17), loc=(0, YR + 0.004, 0.565), material=TRIM)
box("Plate_Rear", (0.32, 0.012, 0.15), loc=(0, YR + 0.011, 0.565), material=PLATE)
text3d("Badge_PONY", "PONY", size=0.055, depth=0.003, loc=(-0.55, YR + 0.004, 0.79),
       rot=(math.pi / 2, 0, math.pi), material=CHROME)
text3d("Badge_HYUNDAI", "HYUNDAI", size=0.035, depth=0.002, loc=(0.52, YR + 0.004, 0.79),
       rot=(math.pi / 2, 0, math.pi), material=CHROME)
cylinder("Exhaust_Pipe", r=0.024, h=0.36, loc=(0.45, 1.87, 0.245), rot=(-math.pi / 2, 0, 0),
         base=False, material=CHROME, seg=20)

# ── 9. 측면 디테일: 몰딩 · 손잡이 · 펜더 미러 · 와이퍼 · 레인 몰딩 ──
for s, tag in ((1, "R"), (-1, "L")):
    for k, y in enumerate((-0.10, 0.70)):
        box(f"Door_Handle_{tag}{k}", (0.016, 0.12, 0.024), loc=(s * (surf_x(y, 0.79) + 0.004), y, 0.79),
            material=CHROME)
    y0, y1 = a_line(BELT) + 0.06, 0.86
    box(f"Belt_Molding_{tag}", (0.010, y1 - y0, 0.012),
        loc=(s * (side_x(BELT + 0.012) + 0.003), (y0 + y1) / 2, BELT + 0.012), material=CHROME)
    box(f"Drip_Rail_{tag}", (0.010, ROOF_Y1 - ROOF_Y0 - 0.02, 0.012),
        loc=(s * (ROOF_HW + 0.002), (ROOF_Y0 + ROOF_Y1) / 2, ROOF - 0.03), material=CHROME)
    # 휠아치 크롬 테두리 대신 문 아래 얇은 사이드 실 몰딩
    yc0, yc1 = AXLE_F + 0.40, AXLE_R - 0.40
    box(f"Sill_Molding_{tag}", (0.012, yc1 - yc0, 0.016),
        loc=(s * (surf_x((yc0 + yc1) / 2, 0.32) + 0.004), (yc0 + yc1) / 2, 0.32), material=CHROME)
    # 펜더 미러: 앞 펜더 위에 선 크롬 기둥과 둥근 거울
    mx, my = s * 0.69, -1.05
    mz = surf_z(mx, my)
    cylinder(f"Fender_Mirror_Stem_{tag}", r=0.009, h=0.12, loc=(mx, my, mz - 0.01), material=CHROME, seg=12)
    cylinder(f"Fender_Mirror_Base_{tag}", r=0.022, h=0.016, loc=(mx, my, mz - 0.006), material=TRIM, seg=16)
    head = cylinder(f"Fender_Mirror_Head_{tag}", r=0.05, h=0.035, loc=(mx, my + 0.006, mz + 0.14),
                    rot=(math.pi / 2, 0, s * math.radians(12)), base=False, material=CHROME, seg=24)
    bevel(head, 0.01, 2)
    cylinder(f"Fender_Mirror_Glass_{tag}", r=0.042, h=0.004, loc=(mx + s * 0.004, my + 0.026, mz + 0.14),
             rot=(math.pi / 2, 0, s * math.radians(12)), base=False, material=LENS, seg=24)

ws_ang = math.atan2(ROOF - BELT, ROOF_Y0 - CAB_Y0)
for k, x in enumerate((-0.30, 0.20)):
    z = 0.935
    n = Vector((0, -math.sin(ws_ang), math.cos(ws_ang)))
    p = Vector((x, a_line(z), z)) + n * 0.014
    box(f"Wiper_{k}", (0.44, 0.012, 0.012), loc=p, rot=(ws_ang, 0, math.radians(4)), material=TRIM)
box("Cowl_Panel", (1.36, 0.10, 0.012), loc=(0, a_line(BELT) - 0.05, surf_z(0, -0.71) + 0.002), material=TRIM)
# 지붕 앞뒤 크롬 몰딩(앞유리·뒷유리 위)


def screen_surround(name, yb, yt, sign, zb_, zt_, inset):
    """앞유리·뒷유리 둘레 크롬 몰딩: 유리 가장자리를 따라 닫힌 관."""
    def y_at(z):
        return yb + (z - BELT) * (yt - yb) / (ROOF - BELT)
    dy, dz = (yt - yb), (ROOF - BELT)
    n = Vector((0, -dz, dy) if sign > 0 else (0, dz, -dy)).normalized()
    pts = []
    for x0, z0 in ((-1, zb_), (1, zb_), (1, zt_), (-1, zt_)):
        p = Vector((x0 * (side_x(z0) - inset), y_at(z0), z0)) + n * 0.006
        pts.append(tuple(p))
    curve_tube(name, pts, radius=0.007, material=CHROME, closed=True, resolution=2, bevel_res=2)


screen_surround("Windshield_Chrome", CAB_Y0, ROOF_Y0, 1, BELT + 0.02, ROOF - 0.04, 0.07)
screen_surround("Rear_Glass_Chrome", CAB_Y1, ROOF_Y1, -1, 0.935, ROOF - 0.045, 0.09)
# 앞 펜더 라디오 안테나
cylinder("Radio_Antenna", r=0.004, h=0.55, loc=(-0.70, -0.80, surf_z(-0.70, -0.80) - 0.01),
         rot=(math.radians(-8), 0, 0), material=CHROME, seg=8)


# ── 10. 마무리: 커브·글자를 메시로 ──────────────────────
def to_mesh(ob):
    dg = bpy.context.evaluated_depsgraph_get()
    me = bpy.data.meshes.new_from_object(ob.evaluated_get(dg))
    new = bpy.data.objects.new(ob.name, me)
    link(new)
    new.matrix_world = ob.matrix_world.copy()
    bpy.data.objects.remove(ob, do_unlink=True)
    return new


for ob in list(bpy.context.scene.objects):
    if ob.type in ('CURVE', 'FONT'):
        to_mesh(ob)
