# 윌리스 MB 지프 (1941~1945, 미 육군 1/4톤 4x4 정찰차)
# - 차체는 옆모습 단면(문 없는 곡선 컷아웃, 뒷바퀴 아치)을 X 방향으로 돌출한 얇은 판들로 짜 맞췄다.
# - 9칸 세로 슬롯 그릴은 앞모습 윤곽을 돌출한 판에서 슬롯과 전조등 구멍을 불리언으로 뚫었다.
# - 바퀴는 타이어 단면을 회전체(lathe)로 돌리고 V자 트레드 블록을 수학 배치한 뒤, 같은 메시를 5개(예비 포함) 공유한다.
# - 접이식 앞유리, 버킷 시트, 운전대, 삽, 제리캔, 보닛의 흰 별 같은 식별 요소는 상자·원기둥·관으로 덧붙였다.
# 단위 m, 실제 크기(전장 약 3.3m, 휠베이스 2.03m). 앞쪽이 -Y, 운전석(좌핸들)은 +X 쪽이다.
import math
from atelier import *

SHOT = {"azimuth": -40, "elevation": 13, "lens": 55, "light": "day", "background": "#28303a"}

# ── 재질 ─────────────────────────────────────────────
OD = mat("OliveDrab_Paint", "#4d5330", 0.62, 0.0)
OD_DARK = mat("OliveDrab_Shadowed", "#3a3f25", 0.7, 0.0)
RUBBER = mat("Tire_Rubber", "#262624", 0.88, 0.0)
STEEL = mat("Dark_Steel", "#3b3b37", 0.5, 0.7)
STAR = mat("Star_White_Stencil", "#dcd8c6", 0.6, 0.0)
GLASS = glass("Windshield_Glass", "#a9c3cc", 0.04)
LENS = mat("Headlamp_Lens", "#d9dccf", 0.12, 0.3)
CHROME = mat("Lamp_Rim_Steel", "#9a9a92", 0.25, 1.0)
CANVAS = mat("Seat_Canvas", "#4a4630", 0.85, 0.0)
RED = mat("Tail_Lens_Red", "#7d1c14", 0.3, 0.0)
WOOD = mat("Tool_Handle_Wood", "#5e4630", 0.7, 0.0)

# ── 치수 ─────────────────────────────────────────────
TIRE_R = 0.36
AXLE_Z = TIRE_R
Y_FA, Y_RA = -1.10, 0.93          # 앞/뒤 차축(휠베이스 2.03m)
TRACK = 0.6225                    # 윤거 1.245m 의 절반
TUB_HALF = 0.72
FLOOR_Z = 0.50
GRILLE_Y = -1.31
HOOD_TOP = 1.05
FENDER_TOP = 0.88


def arc_pts(cy, cz, r, a0, a1, n):
    return [(cy + r * math.cos(math.radians(a0 + (a1 - a0) * i / n)),
             cz + r * math.sin(math.radians(a0 + (a1 - a0) * i / n))) for i in range(n + 1)]


def bake(ob):
    """join 후 오브젝트 변환을 메시에 굽고 원점을 (0,0,0) 으로 되돌린다."""
    ob.data.transform(ob.matrix_basis)
    ob.matrix_basis = Matrix.Identity(4)
    return ob


def side_profile(name, pts, x_center, thick, material):
    """옆모습 (y,z) 다각형을 X 방향 두께 thick 으로 돌출."""
    return extrude_profile(name, pts, thick, loc=(x_center, 0, 0), material=material, axis='X')


# ── 바퀴 (하나 만들고 링크 복제) ─────────────────────
def make_wheel():
    W = 0.088
    prof = [(0.205, -W + 0.005), (0.25, -W - 0.004), (0.31, -W - 0.002), (0.345, -W + 0.012),
            (0.358, -W + 0.03), (0.36, 0.0), (0.358, W - 0.03), (0.345, W - 0.012), (0.31, W + 0.002),
            (0.25, W + 0.004), (0.205, W - 0.005), (0.20, 0.0), (0.205, -W + 0.005)]
    tire = lathe("Tire", prof, material=RUBBER, seg=72)
    parts = [tire]
    n = 24
    lug = box("Lug", (0.022, 0.05, 0.07), material=RUBBER)
    bevel(lug, 0.006, 1)
    apply_all(lug)
    for i in range(n):
        for side in (-1, 1):
            a = 2 * math.pi * (i + (0.5 if side > 0 else 0)) / n
            r = 0.362
            o = duplicate(lug, loc=(r * math.cos(a), r * math.sin(a), side * 0.036),
                          rot=(side * math.radians(28), 0, a), linked=False)
            parts.append(o)
    bpy.data.objects.remove(lug, do_unlink=True)
    # 강철 휠(분할 림): 바깥쪽 = +Z
    rim_prof = [(0.0, 0.035), (0.055, 0.035), (0.07, 0.048), (0.115, 0.048), (0.13, 0.03), (0.17, 0.028),
                (0.19, 0.04), (0.205, 0.07), (0.214, 0.075), (0.214, -0.075), (0.205, -0.07), (0.19, -0.04),
                (0.17, 0.018), (0.13, 0.02), (0.07, 0.025), (0.0, 0.025)]
    rim = lathe("Rim", rim_prof, material=OD, seg=48)
    parts.append(rim)
    for k in range(5):
        a = 2 * math.pi * k / 5
        nut = cylinder("Nut", 0.011, 0.016, loc=(0.092 * math.cos(a), 0.092 * math.sin(a), 0.046),
                       material=STEEL, seg=6)
        parts.append(nut)
    for k in range(8):   # 분할 림 볼트
        a = 2 * math.pi * (k + 0.5) / 8
        parts.append(cylinder("RimBolt", 0.008, 0.012, loc=(0.15 * math.cos(a), 0.15 * math.sin(a), 0.026),
                              material=STEEL, seg=6))
    parts.append(cylinder("HubCap", 0.045, 0.03, loc=(0, 0, 0.046), r2=0.035, material=OD_DARK, seg=24))
    return join(parts, "Wheel")


wheel = make_wheel()
wheel.location = (TRACK, Y_FA, AXLE_Z)
wheel.rotation_euler = (0, math.pi / 2, 0)
wheel.name = "Wheel_FrontLeft"
duplicate(wheel, loc=(-TRACK, Y_FA, AXLE_Z), rot=(0, -math.pi / 2, 0), name="Wheel_FrontRight")
duplicate(wheel, loc=(TRACK, Y_RA, AXLE_Z), rot=(0, math.pi / 2, 0), name="Wheel_RearLeft")
duplicate(wheel, loc=(-TRACK, Y_RA, AXLE_Z), rot=(0, -math.pi / 2, 0), name="Wheel_RearRight")

# ── 프레임·차축·스프링 ──────────────────────────────
for sx in (-1, 1):
    box(f"Frame_Rail_{'L' if sx > 0 else 'R'}", (0.06, 3.02, 0.11), loc=(sx * 0.42, -0.10, 0.40),
        material=STEEL, base=True)
    for ya in (Y_FA, Y_RA):
        box("Leaf_Spring", (0.05, 0.92, 0.035), loc=(sx * 0.42, ya, 0.368), material=STEEL, base=True)
for ya, nm in ((Y_FA, "Front"), (Y_RA, "Rear")):
    cylinder(f"Axle_{nm}", 0.045, 1.12, loc=(0, ya, AXLE_Z), rot=(0, math.pi / 2, 0), material=STEEL,
             base=False, seg=20)
    sphere(f"Differential_{nm}", 0.12, loc=(-0.12, ya, AXLE_Z), material=STEEL, seg=24, rings=12,
           scale=(0.8, 1.0, 1.0))
box("Frame_Crossmember_Front", (0.90, 0.08, 0.10), loc=(0, -1.52, 0.40), material=STEEL, base=True)
cylinder("Driveshaft", 0.03, 1.9, loc=(-0.12, -0.08, 0.38), rot=(math.pi / 2, 0, 0), material=STEEL,
         base=False, seg=12)
cylinder("Transfer_Case", 0.11, 0.30, loc=(-0.12, -0.15, 0.42), rot=(0, math.pi / 2, 0), material=STEEL,
         base=False, seg=16)

# ── 앞 범퍼 ────────────────────────────────────────
bumper = box("Bumper_Front", (1.50, 0.07, 0.15), loc=(0, -1.60, 0.38), material=OD, base=True)
bevel(bumper, 0.01, 1)
for sx in (-1, 1):
    torus("Tow_Hook", 0.045, 0.012, loc=(sx * 0.40, -1.655, 0.455), rot=(0, math.pi / 2, 0),
          material=STEEL, seg=20, rseg=8)
    box("Bumper_Bracket", (0.06, 0.06, 0.10), loc=(sx * 0.42, -1.545, 0.40), material=STEEL, base=True)

# ── 그릴 (9칸 슬롯 + 전조등 구멍) ─────────────────
GW = 0.50
gpts = [(-GW, 0.50), (GW, 0.50), (GW, 0.93)] + \
       [(x, z) for x, z in arc_pts(GW - 0.12, 0.93, 0.12, 0, 90, 8)[1:]] + \
       [(x, z) for x, z in arc_pts(-GW + 0.12, 0.93, 0.12, 90, 180, 8)[:-1]] + [(-GW, 0.93)]
grille = extrude_profile("Grille", gpts, 0.03, loc=(0, GRILLE_Y, 0), material=OD)
cutters = []
for i in range(9):
    x = (i - 4) * 0.066
    c = box("SlotCut", (0.040, 0.2, 0.40), loc=(x, GRILLE_Y, 0.585), material=None, base=True)
    bevel(c, 0.019, 4, angle=10)
    apply_all(c)
    cutters.append(c)
for sx in (-1, 1):
    cutters.append(cylinder("LampCut", 0.092, 0.2, loc=(sx * 0.40, GRILLE_Y, 0.76), rot=(math.pi / 2, 0, 0),
                            base=False, seg=32))
cut = join(cutters, "Grille_Cutter")
boolean(grille, cut)
box("Radiator_Core", (0.62, 0.05, 0.44), loc=(0, GRILLE_Y + 0.07, 0.57), material=STEEL, base=True)
for sx in (-1, 1):
    bucket = cylinder("Headlamp_Bucket", 0.094, 0.10, loc=(sx * 0.40, GRILLE_Y + 0.11, 0.76),
                      rot=(math.pi / 2, 0, 0), material=OD_DARK, seg=32)
    lathe("Headlamp_Lens", [(0.0, -0.012), (0.05, -0.008), (0.078, 0.0), (0.084, 0.008)],
          loc=(sx * 0.40, GRILLE_Y + 0.025, 0.76), rot=(math.pi / 2, 0, 0), material=LENS, seg=32)
    torus("Headlamp_Rim", 0.086, 0.008, loc=(sx * 0.40, GRILLE_Y - 0.017, 0.76), rot=(math.pi / 2, 0, 0),
          material=CHROME, seg=32, rseg=8)

# ── 보닛 ───────────────────────────────────────────
hood = box("Hood", (0.96, 1.03, HOOD_TOP - FENDER_TOP), loc=(0, -0.805, FENDER_TOP), material=OD, base=True)
bevel(hood, 0.018, 3)
for sx in (-1, 1):
    # 보닛 고정 고리
    box("Hood_Latch", (0.012, 0.03, 0.07), loc=(sx * 0.486, -1.05, 0.92), material=STEEL, base=True)
box("Hood_Hinge", (0.90, 0.025, 0.015), loc=(0, -0.30, HOOD_TOP - 0.004), material=STEEL, base=True)

# ── 앞 펜더 (평평한 윗면, 뒤끝이 발판으로 꺾임) ───
fender_top = [(-1.335, 0.81), (-1.315, FENDER_TOP), (-0.58, FENDER_TOP), (-0.47, 0.85), (-0.38, 0.76),
              (-0.32, 0.62), (-0.28, 0.555)]
fender_bot = [(y + 0.0, z - 0.03) for y, z in fender_top]
fender_bot = [(-0.25, 0.53), (-0.30, 0.60), (-0.355, 0.735), (-0.45, 0.82), (-0.575, 0.852), (-1.30, 0.852),
              (-1.31, 0.81)]
fpts = fender_top + fender_bot
for sx in (-1, 1):
    f = side_profile(f"Fender_{'L' if sx > 0 else 'R'}", fpts, sx * 0.615, 0.25, OD)
    bevel(f, 0.006, 1)
    # 펜더 안쪽 에이프런
    box("Fender_Apron", (0.02, 0.92, 0.36), loc=(sx * 0.49, -0.84, FENDER_TOP - 0.36), material=OD_DARK,
        base=True)
    # 등화관제등(블랙아웃 램프)
    box("Blackout_Lamp", (0.07, 0.05, 0.06), loc=(sx * 0.66, -1.24, FENDER_TOP), material=OD_DARK, base=True)
    cylinder("Blackout_Lens", 0.016, 0.006, loc=(sx * 0.66, -1.265, FENDER_TOP + 0.03),
             rot=(math.pi / 2, 0, 0), material=LENS, seg=16, base=False)

# ── 차체 욕조(tub) ────────────────────────────────
ARCH_R = 0.44
th0 = math.degrees(math.asin((FLOOR_Z - AXLE_Z) / ARCH_R))
TUB_FRONT, TUB_REAR, TUB_TOP = -0.30, 1.395, 0.98
tub_side = [(TUB_FRONT, FLOOR_Z), (Y_RA - ARCH_R * math.cos(math.radians(th0)), FLOOR_Z)]
tub_side += arc_pts(Y_RA, AXLE_Z, ARCH_R, 180 - th0, th0, 18)[1:-1]
tub_side += [(Y_RA + ARCH_R * math.cos(math.radians(th0)), FLOOR_Z), (TUB_REAR, FLOOR_Z), (TUB_REAR, TUB_TOP),
             (0.64, TUB_TOP), (0.58, 0.965), (0.52, 0.92), (0.46, 0.84), (0.39, 0.76), (0.30, 0.71),
             (0.18, 0.69), (0.02, 0.69), (-0.09, 0.71), (-0.16, 0.76), (-0.20, 0.84), (-0.225, 0.94),
             (-0.24, 1.0), (-0.26, 1.03), (TUB_FRONT, 1.03)]
for sx in (-1, 1):
    s = side_profile(f"Body_Side_{'L' if sx > 0 else 'R'}", tub_side, sx * (TUB_HALF - 0.015), 0.03, OD)
    bevel(s, 0.008, 2)
    # 바깥 모서리를 감싸는 말린 테두리
    top_edge = [(sx * TUB_HALF, y, z + 0.004) for y, z in tub_side[tub_side.index((TUB_REAR, TUB_TOP)):]]
    curve_tube("Body_Rolled_Edge", top_edge, radius=0.011, material=OD)
box("Body_Floor", (0.94, TUB_REAR - TUB_FRONT, 0.03), loc=(0, (TUB_REAR + TUB_FRONT) / 2, FLOOR_Z),
    material=OD_DARK, base=True)
fy1 = Y_RA - ARCH_R * math.cos(math.radians(th0))
for sx in (-1, 1):
    box("Body_Floor_Side", (0.23, fy1 - TUB_FRONT, 0.03), loc=(sx * 0.585, (fy1 + TUB_FRONT) / 2, FLOOR_Z),
        material=OD_DARK, base=True)
    # 뒷바퀴 휠하우스(안쪽 아치 판 + D자 벽)
    hw = arc_pts(Y_RA, AXLE_Z, ARCH_R, 180 - th0, th0, 18)
    strip = hw + list(reversed(arc_pts(Y_RA, AXLE_Z, ARCH_R + 0.02, 180 - th0, th0, 18)))
    side_profile("Wheelhouse_Arch", strip, sx * 0.585, 0.22, OD_DARK)
    dwall = arc_pts(Y_RA, AXLE_Z, ARCH_R + 0.02, 180 - th0, th0, 18)
    side_profile("Wheelhouse_Wall", dwall, sx * 0.475, 0.02, OD_DARK)
rear = box("Body_Rear_Panel", (2 * TUB_HALF, 0.03, TUB_TOP - FLOOR_Z), loc=(0, TUB_REAR - 0.015, FLOOR_Z),
           material=OD, base=True)
bevel(rear, 0.008, 2)
curve_tube("Body_Rolled_Edge_Rear", [(-TUB_HALF, TUB_REAR, TUB_TOP + 0.004), (TUB_HALF, TUB_REAR, TUB_TOP + 0.004)],
           radius=0.011, material=OD)
cowl = box("Cowl", (2 * TUB_HALF - 0.03, 0.17, 1.03 - FLOOR_Z), loc=(0, -0.215, FLOOR_Z), material=OD, base=True)
bevel(cowl, 0.012, 2)

# ── 계기판 ─────────────────────────────────────────
for x in (0.10, 0.22, 0.34, -0.05):
    cylinder("Gauge", 0.035, 0.012, loc=(x, -0.13, 0.90), rot=(-math.pi / 2, 0, 0), material=CHROME, seg=20)
    cylinder("Gauge_Face", 0.028, 0.004, loc=(x, -0.118, 0.90), rot=(-math.pi / 2, 0, 0), material=STEEL, seg=20)
box("Glovebox_Plate", (0.30, 0.008, 0.16), loc=(-0.38, -0.128, 0.82), material=OD_DARK, base=True)

# ── 앞유리 (접이식 프레임) ─────────────────────────
WS_Y, WS_Z, WS_H, WS_W = -0.205, 1.03, 0.66, 1.34
ws = [box("WS_Bottom", (WS_W, 0.05, 0.05), loc=(0, 0, 0), material=OD, base=True),
      box("WS_Top", (WS_W, 0.04, 0.045), loc=(0, 0, WS_H - 0.045), material=OD, base=True),
      box("WS_Post_L", (0.05, 0.05, WS_H), loc=(WS_W / 2 - 0.025, 0, 0), material=OD, base=True),
      box("WS_Post_R", (0.05, 0.05, WS_H), loc=(-WS_W / 2 + 0.025, 0, 0), material=OD, base=True),
      box("WS_Center", (0.03, 0.04, WS_H - 0.09), loc=(0, 0, 0.045), material=OD, base=True)]
for o in ws:
    bevel(o, 0.006, 1)
frame = bake(join(ws, "Windshield_Frame"))
frame.location = (0, WS_Y, WS_Z)
frame.rotation_euler = (math.radians(-9), 0, 0)
panes = [box("Pane_L", (0.585, 0.008, WS_H - 0.095), loc=(0.31, 0, 0.05), material=GLASS, base=True),
         box("Pane_R", (0.585, 0.008, WS_H - 0.095), loc=(-0.31, 0, 0.05), material=GLASS, base=True)]
glass_obj = bake(join(panes, "Windshield_Glass"))
glass_obj.location = (0, WS_Y, WS_Z)
glass_obj.rotation_euler = (math.radians(-9), 0, 0)
wip = []
for x in (0.31, -0.31):
    wip.append(box("Wiper_Arm", (0.012, 0.012, 0.32), loc=(x + 0.05, -0.03, WS_H - 0.36), rot=(0, 0.35, 0),
                   material=STEEL, base=True))
    wip.append(cylinder("Wiper_Motor", 0.03, 0.06, loc=(x, -0.02, WS_H - 0.06), rot=(math.pi / 2, 0, 0),
                        material=STEEL, base=False, seg=12))
wipers = bake(join(wip, "Windshield_Wipers"))
wipers.location = (0, WS_Y, WS_Z)
wipers.rotation_euler = (math.radians(-9), 0, 0)
for sx in (-1, 1):   # 앞유리 고정 걸쇠
    box("WS_Hinge", (0.05, 0.06, 0.04), loc=(sx * 0.62, WS_Y, WS_Z - 0.01), material=STEEL, base=True)

# ── 운전대 (좌핸들, +X 쪽) ─────────────────────────
DX = 0.33
col_tilt = math.radians(-52)
cylinder("Steering_Column", 0.022, 0.40, loc=(DX, -0.13, 0.80), rot=(col_tilt, 0, 0), material=STEEL, seg=12)
d = Vector((0, -math.sin(col_tilt), math.cos(col_tilt)))
wc = Vector((DX, -0.13, 0.80)) + d * 0.40
torus("Steering_Wheel", 0.19, 0.012, loc=wc, rot=(col_tilt, 0, 0), material=STEEL, seg=40, rseg=8)
for k in range(3):
    a = math.radians(90 + 120 * k)
    sp = box("Steering_Spoke", (0.19, 0.012, 0.008), material=STEEL)
    sp.data.transform(Matrix.Translation((0.095, 0, 0)))
    sp.location = wc
    m = Euler((col_tilt, 0, 0)).to_matrix() @ Matrix.Rotation(a, 3, 'Z')
    sp.rotation_euler = m.to_euler()

# ── 좌석 ───────────────────────────────────────────
for sx in (-1, 1):
    x = sx * 0.33
    box("Seat_Frame", (0.38, 0.36, 0.10), loc=(x, 0.46, FLOOR_Z + 0.03), material=STEEL, base=True)
    c = box("Seat_Cushion", (0.44, 0.42, 0.10), loc=(x, 0.46, FLOOR_Z + 0.13), material=CANVAS, base=True)
    bevel(c, 0.03, 3)
    b = box("Seat_Back", (0.44, 0.08, 0.42), loc=(x, 0.70, FLOOR_Z + 0.22), rot=(math.radians(-10), 0, 0),
            material=CANVAS, base=True)
    bevel(b, 0.03, 3)
rc = box("Rear_Seat_Cushion", (0.90, 0.38, 0.10), loc=(0, 1.14, FLOOR_Z + 0.16), material=CANVAS, base=True)
bevel(rc, 0.03, 3)
box("Rear_Seat_Frame", (0.86, 0.34, 0.13), loc=(0, 1.14, FLOOR_Z + 0.03), material=STEEL, base=True)
rb = box("Rear_Seat_Back", (0.90, 0.07, 0.30), loc=(0, 1.33, FLOOR_Z + 0.26), material=CANVAS, base=True)
bevel(rb, 0.025, 3)

# ── 뒤: 예비 타이어, 제리캔, 미등, 견인 고리 ───────
spare = duplicate(wheel, loc=(-0.22, TUB_REAR + 0.105, 0.70), rot=(-math.pi / 2, 0, 0), name="Wheel_Spare")
box("Spare_Bracket", (0.10, 0.04, 0.10), loc=(-0.22, TUB_REAR + 0.01, 0.65), material=STEEL, base=True)
can = box("Jerry_Can", (0.345, 0.165, 0.47), loc=(0.40, TUB_REAR + 0.105, 0.50), material=OD, base=True)
bevel(can, 0.02, 2)
for k in (-1, 0, 1):
    box("Jerry_Can_Handle", (0.03, 0.06, 0.03), loc=(0.40 + k * 0.06, TUB_REAR + 0.105, 0.97), material=OD,
        base=True)
box("Jerry_Can_Cap", (0.05, 0.05, 0.05), loc=(0.53, TUB_REAR + 0.105, 0.96), material=OD_DARK, base=True)
for sx in (-1, 1):
    box("Can_Strap", (0.025, 0.19, 0.03), loc=(0.40 + sx * 0.12, TUB_REAR + 0.105, 0.80), material=STEEL,
        base=True)
box("Jerry_Can_Holder", (0.38, 0.03, 0.06), loc=(0.40, TUB_REAR + 0.015, 0.50), material=STEEL, base=True)
for sx in (-1, 1):
    box("Tail_Lamp", (0.07, 0.04, 0.10), loc=(sx * 0.63, TUB_REAR + 0.02, 0.80), material=OD_DARK, base=True)
    box("Tail_Lens", (0.05, 0.006, 0.035), loc=(sx * 0.63, TUB_REAR + 0.042, 0.84), material=RED, base=True)
    box("Bumperette", (0.10, 0.10, 0.12), loc=(sx * 0.58, TUB_REAR + 0.05, 0.40), material=OD, base=True)
torus("Pintle_Hook", 0.05, 0.014, loc=(0.0, TUB_REAR + 0.07, 0.43), rot=(0, math.pi / 2, 0), material=STEEL,
      seg=20, rseg=8)

# ── 보닛의 흰 별 + 차량 번호 ───────────────────────
def star(name, R, loc, rot=(0, 0, 0), h=0.003):
    pts = []
    for k in range(10):
        r = R if k % 2 == 0 else R * 0.382
        a = math.radians(90 + 36 * k)
        pts.append((r * math.cos(a), r * math.sin(a)))
    return prism(name, pts, h, loc=loc, rot=rot, material=STAR)


star("Hood_Star", 0.20, (0, -0.78, HOOD_TOP + 0.0005))
for sx in (-1, 1):
    t = text3d("Hood_Serial", "USA  20128435", size=0.055, depth=0.001,
               loc=(sx * 0.4815, -0.78, 0.945), rot=(math.pi / 2, 0, sx * math.pi / 2), material=STAR)

# ── 왼쪽(운전석) 옆의 삽과 도끼 ─────────────────────
box("Shovel_Handle", (0.03, 0.58, 0.03), loc=(TUB_HALF + 0.02, 0.01, 0.585), material=WOOD, base=False)
box("Shovel_Blade", (0.014, 0.20, 0.17), loc=(TUB_HALF + 0.012, 0.39, 0.59), material=STEEL, base=False)
box("Axe_Handle", (0.03, 0.55, 0.028), loc=(TUB_HALF + 0.02, 0.0, 0.645), material=WOOD, base=False)
box("Axe_Head", (0.024, 0.08, 0.15), loc=(TUB_HALF + 0.02, -0.30, 0.645), material=STEEL, base=False)
for y in (-0.15, 0.25):
    box("Tool_Clamp", (0.04, 0.03, 0.11), loc=(TUB_HALF + 0.015, y, 0.61), material=STEEL, base=False)

# ── 손잡이 ─────────────────────────────────────────
for sx in (-1, 1):
    x = sx * (TUB_HALF + 0.03)
    curve_tube("Grab_Handle", [(sx * TUB_HALF, 1.02, 0.88), (x, 1.04, 0.885), (x, 1.22, 0.885),
                               (sx * TUB_HALF, 1.24, 0.88)], radius=0.011, material=OD)

# ── 엔진·배기·변속 레버·백미러 ─────────────────────
eng = box("Engine_Block", (0.46, 0.78, 0.36), loc=(0, -0.80, 0.50), material=STEEL, base=True)
bevel(eng, 0.03, 2)
box("Oil_Pan", (0.34, 0.50, 0.08), loc=(0, -0.88, 0.43), material=STEEL, base=True)
cylinder("Exhaust_Pipe", 0.022, 1.55, loc=(-0.30, 0.55, 0.42), rot=(math.pi / 2, 0, 0), material=STEEL,
         base=False, seg=12)
cylinder("Muffler", 0.075, 0.45, loc=(-0.30, 0.30, 0.42), rot=(math.pi / 2, 0, 0), material=STEEL,
         base=False, seg=20)
for k, (dx, ang) in enumerate(((0.06, -0.25), (0.11, -0.05), (0.15, 0.15))):
    lv = cylinder("Shift_Lever", 0.009, 0.38 - k * 0.06, loc=(dx, 0.02, FLOOR_Z + 0.03), rot=(ang, 0, 0),
                  material=STEEL, seg=8)
    sphere("Shift_Knob", 0.022, loc=(dx, 0.02 - math.sin(ang) * (0.38 - k * 0.06),
                                     FLOOR_Z + 0.03 + math.cos(ang) * (0.38 - k * 0.06)),
           material=OD_DARK, seg=12, rings=8)
cylinder("Hand_Brake", 0.01, 0.20, loc=(0.0, -0.13, 0.80), rot=(-math.pi / 2, 0, 0), material=STEEL, seg=8)
mir = bake(join([cylinder("Mirror_Arm", 0.007, 0.16, loc=(0, 0, 0), rot=(0, math.radians(60), 0),
                          material=STEEL, seg=8),
                 cylinder("Mirror_Head", 0.055, 0.015, loc=(0.135, 0, 0.08), rot=(math.pi / 2, 0, 0),
                          material=STEEL, base=False, seg=20)], "Rearview_Mirror"))
mir.location = (WS_W / 2, WS_Y + 0.03, WS_Z + 0.45)
mir.rotation_euler = (math.radians(-9), 0, 0)
for sx in (-1, 1):   # 앞유리를 눕혔을 때 받치는 고무 받침
    cylinder("Windshield_Rest", 0.025, 0.02, loc=(sx * 0.30, -0.95, HOOD_TOP), material=RUBBER, seg=16)
    # 보닛 위 걸쇠
    box("Hood_Clamp", (0.04, 0.05, 0.012), loc=(sx * 0.40, -1.24, HOOD_TOP), material=STEEL, base=True)

# ── 접어 내린 캔버스 지붕과 지붕 활대 ───────────────
roll = cylinder("Canvas_Top_Folded", 0.065, 1.30, loc=(0, 1.27, TUB_TOP + 0.066), rot=(0, math.pi / 2, 0),
                material=CANVAS, base=False, seg=24)
smooth(roll)
for k, (yb, zb) in enumerate(((1.06, 0.0), (1.12, 0.03))):
    pts = [(-TUB_HALF - 0.012, yb, TUB_TOP - 0.06)]
    pts += [(-TUB_HALF - 0.012, yb + 0.05, TUB_TOP + 0.02 + zb), (-TUB_HALF - 0.012, 1.42 + 0.03 * k, TUB_TOP + 0.03 + zb),
            (TUB_HALF + 0.012, 1.42 + 0.03 * k, TUB_TOP + 0.03 + zb), (TUB_HALF + 0.012, yb + 0.05, TUB_TOP + 0.02 + zb),
            (TUB_HALF + 0.012, yb, TUB_TOP - 0.06)]
    curve_tube(f"Top_Bow_{k + 1}", pts, radius=0.011, material=STEEL)
for sx in (-1, 1):
    box("Top_Bow_Bracket", (0.02, 0.10, 0.08), loc=(sx * (TUB_HALF + 0.008), 1.09, TUB_TOP - 0.10),
        material=STEEL, base=True)
