# 런던 루트마스터 2층 버스 (AEC Routemaster RM형, 1956~1968 생산)
# 차체는 "둥근 모서리 직사각형 평면"을 높이별로 쌓아 잇는 로프트(loft)로 만들었다.
# 위층은 지붕으로 갈수록 평면을 안쪽으로 줄여 앞뒤가 둥근 돔 지붕을 얻고,
# 아래층은 운전석(하프 캡)·보닛·앞 펜더 덩어리와 객실 덩어리로 나눈 뒤
# 불리언으로 바퀴 아치와 뒤쪽 개방형 승강장(오픈 플랫폼)을 잘라 냈다.
# 창·행선판·전조등·그릴은 표면에 붙인 얇은 판과 원통이며, 바퀴는 회전체(lathe)를 링크 복제했다.
# 실제 치수: 길이 약 8.38m, 너비 2.44m, 높이 약 4.38m, 축간거리 약 5.13m. 정면은 -Y.
from atelier import *

SHOT = {"azimuth": -46, "elevation": 9, "lens": 50, "light": "studio", "background": "#2b3442"}

# ---------------------------------------------------------------- 재질
RED = mat('London Transport red', '#a8161b', rough=0.32, coat=0.6)
CREAM = mat('Cream relief band', '#d8c79c', rough=0.4)
GLASS = mat('Window glass (tinted)', '#17212a', rough=0.04)
TYRE = mat('Tyre rubber', '#1d1d1f', rough=0.85)
WHEEL = mat('Wheel steel (dark grey)', '#3a3c40', rough=0.45, metal=0.4)
CHROME = mat('Chrome', '#c9ced3', rough=0.12, metal=1.0)
INTERIOR = mat('Platform interior cream', '#c2b58f', rough=0.6)
UNDER = mat('Chassis black', '#232528', rough=0.8)
BLIND = mat('Destination blind', '#121418', rough=0.3)
GOLD = mat('Gold fleetname', '#c49a45', rough=0.3, metal=0.8)
LENS = mat('Lamp lens and blind lettering', '#f1ead2', rough=0.1, emit='#fff4d8', emit_strength=0.5)
BLINDTXT = LENS
TAIL = mat('Tail lamp red', '#8e1414', rough=0.15, emit='#b01818', emit_strength=0.5)
FLOOR = mat('Platform floor (slatted grey)', '#585b5e', rough=0.75)

# ---------------------------------------------------------------- 주요 치수
L, W = 8.38, 2.44
HX = W / 2
YF, YR = -L / 2, L / 2          # 앞(-Y) / 뒤(+Y)
FRONT_AXLE = YF + 1.25
REAR_AXLE = FRONT_AXLE + 5.13
WHEEL_R = 0.50
BODY_BOT = 0.38
DECK = 2.20                      # 위층 차체 시작 높이
TOP = 4.38
BULKHEAD = YF + 1.55             # 아래층 객실 앞벽
PLAT_Y = 2.95                    # 승강장 시작
PLAT_X = 0.12


def rrect(hx, yf, yr, rf, rr, n=8, xo=0.0):
    """둥근 모서리 직사각형(앞 모서리 반지름 rf, 뒤 rr) 점 목록, 반시계."""
    rf, rr = max(rf, 0.004), max(rr, 0.004)
    pts = []
    for cx, cy, r, a0 in ((hx - rr, yr - rr, rr, 0), (-hx + rr, yr - rr, rr, 90),
                          (-hx + rf, yf + rf, rf, 180), (hx - rf, yf + rf, rf, 270)):
        for i in range(n + 1):
            a = math.radians(a0 + 90 * i / n)
            pts.append((xo + cx + r * math.cos(a), cy + r * math.sin(a)))
    return pts


def loft(name, levels, material, n=8, sharp=35):
    """levels=[(z, hx, yf, yr, rf, rr), ...] 를 아래에서 위로 이어 닫힌 메시를 만든다."""
    bm = bmesh.new()
    rings = []
    for z, hx, yf, yr, rf, rr in levels:
        rings.append([bm.verts.new((x, y, z)) for x, y in rrect(hx, yf, yr, rf, rr, n)])
    m = len(rings[0])
    for a, b in zip(rings, rings[1:]):
        for i in range(m):
            bm.faces.new((a[i], a[(i + 1) % m], b[(i + 1) % m], b[i]))
    bm.faces.new(list(reversed(rings[0])))
    bm.faces.new(rings[-1])
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
    me = bpy.data.meshes.new(name)
    bm.to_mesh(me)
    bm.free()
    ob = bpy.data.objects.new(name, me)
    link(ob)
    assign(ob, material)
    smooth(ob)
    me.set_sharp_from_angle(angle=math.radians(sharp))
    return ob


def plate(name, c, u, v, w, h, r, t, material, n=5):
    """중심 c, 가로축 u, 세로축 v 위의 둥근 모서리 판(두께 t, 법선 u×v)."""
    c, u, v = Vector(c), Vector(u).normalized(), Vector(v).normalized()
    nrm = u.cross(v)
    r = min(r, w / 2 - 1e-3, h / 2 - 1e-3)
    pts2 = rrect(w / 2, -h / 2, h / 2, r, r, n)
    bm = bmesh.new()
    lo = [bm.verts.new(c + u * px + v * py - nrm * t / 2) for px, py in pts2]
    hi = [bm.verts.new(c + u * px + v * py + nrm * t / 2) for px, py in pts2]
    bm.faces.new(list(reversed(lo)))
    bm.faces.new(hi)
    k = len(pts2)
    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)
    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)


# 면 방향 별 (가로축 u, 세로축 v) — 법선 = u×v 가 바깥을 향한다
NEAR = ((0, 1, 0), (0, 0, 1))     # +X (승강장 쪽, 영국 기준 좌측/보도 쪽)
OFF = ((0, -1, 0), (0, 0, 1))     # -X (운전석 쪽)
FRONT = ((1, 0, 0), (0, 0, 1))    # -Y
REAR = ((-1, 0, 0), (0, 0, 1))    # +Y
ZUP = ((1, 0, 0), (0, 1, 0))


def side_pane(name, side, y0, y1, z0, z1, x=HX, mat_=GLASS, r=0.07, t=0.016):
    s = 1 if side > 0 else -1
    uv = NEAR if s > 0 else OFF
    return plate(name, (s * x, (y0 + y1) / 2, (z0 + z1) / 2), *uv, abs(y1 - y0), z1 - z0, r, t, mat_)


def end_pane(name, face, y, x0, x1, z0, z1, mat_=GLASS, r=0.07, t=0.016):
    uv = FRONT if face < 0 else REAR
    return plate(name, ((x0 + x1) / 2, y, (z0 + z1) / 2), *uv, x1 - x0, z1 - z0, r, t, mat_)


# ---------------------------------------------------------------- 위층 차체 (돔 지붕)
up_levels = [(DECK, HX, YF + 0.02, YR, 0.36, 0.55), (3.68, HX - 0.015, YF + 0.03, YR, 0.36, 0.55)]
for i in range(1, 13):
    t = i / 12
    s = 1 - math.sqrt(1 - t * t)
    up_levels.append((3.68 + (TOP - 3.68) * t, HX - 0.015 - 0.52 * s, YF + 0.03 + 0.62 * s,
                      YR - 0.58 * s, 0.36 + 0.1 * s, 0.55 - 0.1 * s))
upper = loft('Upper deck body', up_levels, RED, n=10)

# 위·아래층 사이 크림색 띠
band = loft('Between-decks cream band', [(DECK + 0.015, HX + 0.008, YF + 0.012, YR + 0.008, 0.37, 0.56),
                                        (DECK + 0.125, HX + 0.008, YF + 0.012, YR + 0.008, 0.37, 0.56)], CREAM, n=10)

# ---------------------------------------------------------------- 아래층 객실
lower = loft('Lower saloon body', [(BODY_BOT, HX - 0.03, BULKHEAD, YR - 0.02, 0.05, 0.53),
                                   (BODY_BOT + 0.1, HX, BULKHEAD, YR, 0.05, 0.55),
                                   (DECK + 0.05, HX, BULKHEAD, YR, 0.05, 0.55)], RED, n=10)

# 앞부분: 보닛·펜더·운전석 아랫단
nose = loft('Bonnet and front wings', [(BODY_BOT, HX - 0.03, YF + 0.04, BULKHEAD + 0.05, 0.2, 0.02),
                                      (BODY_BOT + 0.1, HX, YF + 0.02, BULKHEAD + 0.05, 0.22, 0.02),
                                      (1.24, HX, YF + 0.02, BULKHEAD + 0.05, 0.22, 0.02),
                                      (1.30, HX - 0.04, YF + 0.06, BULKHEAD + 0.05, 0.2, 0.02)], RED, n=8)

# 하프 캡 운전석 (오른쪽 = -X)
CAB_IN = -0.30
cab = box('Half cab', (HX + CAB_IN + 0.0, BULKHEAD - YF + 0.03, DECK + 0.05 - 1.26),
          ((-HX + CAB_IN) / 2, (YF + 0.02 + BULKHEAD + 0.05) / 2, (1.26 + DECK + 0.05) / 2), material=RED)
bevel(cab, 0.06, 3)

# 바퀴 아치와 승강장 절단
ARCH = UNDER
cutters = []
for nm, y in (('front', FRONT_AXLE), ('rear', REAR_AXLE)):
    c = cylinder(f'Cutter wheel arch {nm}', r=0.63, h=3.0, loc=(0, y, WHEEL_R), rot=(0, math.pi / 2, 0),
                 base=False, seg=48, material=ARCH)
    cutters.append(c)
plat_cut = box('Cutter rear platform', (2.0, 2.0, DECK + 0.1 - 0.50),
               (PLAT_X + 1.0, PLAT_Y + 1.0, (0.50 + DECK + 0.1) / 2), material=INTERIOR)
for ob, cs in ((lower, cutters + [plat_cut]), (nose, cutters[:1])):
    for c in cs:
        boolean(ob, c)
        ob.modifiers[-1].material_mode = 'TRANSFER'
apply_all(lower)
apply_all(nose)
for c in cutters + [plat_cut]:
    bpy.data.objects.remove(c, do_unlink=True)
# 승강장 바닥면은 회색으로
for p in lower.data.polygons:
    if p.material_index == lower.data.materials.find('Platform interior cream') and p.normal.z > 0.9:
        if 'Platform floor (slatted grey)' not in lower.data.materials:
            lower.data.materials.append(FLOOR)
        p.material_index = lower.data.materials.find('Platform floor (slatted grey)')

# 섀시 하부
box('Chassis underframe', (1.2, 7.4, 0.2), (0, 0.1, 0.31), material=UNDER)

# ---------------------------------------------------------------- 창문
UP_Z0, UP_Z1 = 2.76, 3.60
LO_Z0, LO_Z1 = 1.44, 2.12
bar_src = None
# 위층 옆창 6칸 (양쪽), 위쪽 1/3 은 내림창 가로대
y_a, y_b = YF + 0.40, YR - 0.58
pitch = (y_b - y_a) / 6
for side in (1, -1):
    tag = 'nearside' if side > 0 else 'offside'
    for i in range(6):
        y0 = y_a + i * pitch + 0.07
        y1 = y_a + (i + 1) * pitch - 0.07
        side_pane(f'Upper window {tag} {i + 1}', side, y0, y1, UP_Z0, UP_Z1)
        side_pane(f'Upper window drop-bar {tag} {i + 1}', side, y0 + 0.02, y1 - 0.02, 3.34, 3.37,
                  x=HX + 0.01, mat_=CHROME, r=0.01, t=0.012)
# 위층 앞창 2장, 뒤 비상창
for i, (x0, x1) in enumerate(((-0.86, -0.045), (0.045, 0.86))):
    end_pane(f'Upper front window {i + 1}', -1, YF + 0.03, x0, x1, UP_Z0, UP_Z1, r=0.09)
end_pane('Upper rear emergency window', 1, YR, -0.6, 0.6, 2.82, 3.46, r=0.09)

# 아래층 옆창: 보도 쪽 4칸, 운전석 쪽 4칸 + 계단창
la, lb = BULKHEAD + 0.12, PLAT_Y - 0.08
lp = (lb - la) / 4
for side in (1, -1):
    tag = 'nearside' if side > 0 else 'offside'
    for i in range(4):
        y0, y1 = la + i * lp + 0.06, la + (i + 1) * lp - 0.06
        side_pane(f'Lower window {tag} {i + 1}', side, y0, y1, LO_Z0, LO_Z1)
        side_pane(f'Lower window drop-bar {tag} {i + 1}', side, y0 + 0.02, y1 - 0.02, 1.90, 1.93,
                  x=HX + 0.01, mat_=CHROME, r=0.01, t=0.012)
side_pane('Lower window offside staircase', -1, PLAT_Y + 0.06, YR - 0.62, LO_Z0 + 0.12, LO_Z1)
end_pane('Lower rear window', 1, YR, -0.64, -0.02, 1.50, 2.10)

# 운전석 창
end_pane('Cab windscreen', -1, YF + 0.02, -HX + 0.08, CAB_IN - 0.07, 1.46, 2.12, r=0.05)
side_pane('Cab offside window', -1, YF + 0.20, BULKHEAD - 0.25, 1.46, 2.12)
side_pane('Cab nearside window', 1, YF + 0.20, BULKHEAD - 0.12, 1.46, 2.12, x=CAB_IN)
# 객실 앞벽 창(보닛 위로 보이는)
end_pane('Lower saloon front bulkhead window', -1, BULKHEAD, CAB_IN + 0.12, HX - 0.14, 1.46, 2.10)

# ---------------------------------------------------------------- 행선판
end_pane('Front destination blind box', -1, YF + 0.02, -0.82, 0.40, 2.30, 2.62, mat_=BLIND, r=0.03, t=0.05)
end_pane('Front route number box', -1, YF + 0.02, 0.48, 0.86, 2.30, 2.62, mat_=BLIND, r=0.03, t=0.05)
text3d('Front blind text', 'TRAFALGAR SQUARE', 0.105, 0.003, (-0.21, YF - 0.008, 2.425), (math.pi / 2, 0, 0), BLINDTXT)
text3d('Front route number', '15', 0.22, 0.003, (0.67, YF - 0.008, 2.385), (math.pi / 2, 0, 0), BLINDTXT)
side_pane('Side route blind box', 1, BULKHEAD + 0.15, BULKHEAD + 1.25, 2.38, 2.62, x=HX + 0.005, mat_=BLIND, r=0.03, t=0.03)
text3d('Side blind text', '15  ALDWYCH', 0.12, 0.003, (HX + 0.024, BULKHEAD + 0.70, 2.455), (math.pi / 2, 0, math.pi / 2), BLINDTXT)
end_pane('Rear route number box', 1, YR, 0.15, 0.55, 2.38, 2.62, mat_=BLIND, r=0.03, t=0.04)
text3d('Rear route number', '15', 0.17, 0.003, (0.35, YR + 0.024, 2.43), (math.pi / 2, 0, math.pi), BLINDTXT)

# 금박 사명 "LONDON TRANSPORT"
for side in (1, -1):
    text3d(f'Fleetname {"nearside" if side > 0 else "offside"}', 'LONDON TRANSPORT', 0.18, 0.002,
           (side * (HX + 0.004), 0.0, 1.05), (math.pi / 2, 0, side * math.pi / 2), GOLD)

# ---------------------------------------------------------------- 앞면: 그릴·전조등·번호판
GX0, GX1 = -0.20, 0.50
end_pane('Radiator grille surround', -1, YF + 0.02, GX0, GX1, 0.52, 1.20, mat_=CHROME, r=0.08, t=0.03)
end_pane('Radiator grille core', -1, YF + 0.005, GX0 + 0.05, GX1 - 0.05, 0.57, 1.15, mat_=UNDER, r=0.05, t=0.03)
slat = box('Grille slat', (0.012, 0.02, 0.56), (GX0 + 0.08, YF - 0.012, 0.86), material=CHROME)
for i in range(1, 14):
    duplicate(slat, loc=(GX0 + 0.08 + i * 0.041, YF - 0.012, 0.86), name='Grille slat')
lathe('Grille roundel badge', [(0, 0), (0.07, 0), (0.075, 0.012), (0, 0.016)], loc=(0.15, YF - 0.005, 1.27),
      rot=(math.pi / 2, 0, 0), material=CHROME, seg=32)
for nm, x in (('nearside', 0.92), ('offside', -0.92)):
    lathe(f'Headlamp rim {nm}', [(0, 0), (0.115, 0), (0.12, 0.03), (0.105, 0.05), (0, 0.05)],
          loc=(x, YF + 0.05, 0.86), rot=(math.pi / 2, 0, 0), material=CHROME, seg=32)
    lathe(f'Headlamp lens {nm}', [(0, 0), (0.096, 0), (0.09, 0.012), (0.05, 0.022), (0, 0.025)],
          loc=(x, YF + 0.003, 0.86), rot=(math.pi / 2, 0, 0), material=LENS, seg=32)
    lathe(f'Sidelamp {nm}', [(0, 0), (0.04, 0), (0.035, 0.02), (0, 0.025)],
          loc=(x, YF + 0.04, 1.08), rot=(math.pi / 2, 0, 0), material=LENS, seg=20)
end_pane('Front number plate', -1, YF + 0.03, -0.62, -0.24, 0.48, 0.60, mat_=BLIND, r=0.01, t=0.02)
text3d('Front registration', 'WLT 615', 0.07, 0.002, (-0.43, YF + 0.012, 0.515), (math.pi / 2, 0, 0), BLINDTXT)
box('Front valance', (2.2, 0.05, 0.08), (0, YF + 0.06, 0.40), material=UNDER)

# 뒷면 등화·번호판
for nm, x in (('offside', -0.85),):
    plate('Tail lamp offside', (x, YR - 0.01, 1.05), *REAR, 0.14, 0.22, 0.05, 0.04, TAIL)
end_pane('Rear number plate', 1, YR + 0.005, -0.62, -0.20, 0.62, 0.74, mat_=BLIND, r=0.01, t=0.02)
text3d('Rear registration', 'WLT 615', 0.07, 0.002, (-0.41, YR + 0.017, 0.655), (math.pi / 2, 0, math.pi), BLINDTXT)
plate('Tail lamp platform', (PLAT_X + 0.08, YR - 0.20, 1.05), *NEAR, 0.10, 0.18, 0.04, 0.03, TAIL)

# ---------------------------------------------------------------- 승강장
plate('Platform ceiling', (PLAT_X + 0.55, PLAT_Y + 0.62, DECK - 0.006), *ZUP, 1.08, 1.22, 0.02, 0.008, INTERIOR)
plate('Saloon doorway', (PLAT_X + 0.62, PLAT_Y + 0.006, 1.26), *REAR, 0.78, 1.42, 0.06, 0.008, UNDER)
for k in range(4):
    top = 0.50 + 0.21 * (k + 1)
    depth = (4 - k) * 0.21
    box(f'Staircase step {k + 1}', (depth, 0.62, top - 0.49), (PLAT_X - 0.01 + depth / 2, YR - 0.42, (top + 0.49) / 2),
        material=FLOOR)
    box(f'Staircase nosing {k + 1}', (depth, 0.63, 0.02), (PLAT_X - 0.01 + depth / 2, YR - 0.42, top + 0.005),
        material=CHROME)
cylinder('Platform grab pole', r=0.022, h=DECK - 0.5, loc=(1.0, 3.93, 0.5), material=CHROME, seg=16)
curve_tube('Platform grab rail', [(1.0, 3.93, 1.75), (PLAT_X + 0.03, 3.93, 1.75)], radius=0.016, material=CHROME)
box('Platform step edge', (0.56, 0.04, 0.03), (PLAT_X + 0.29, YR - 0.03, 0.495), material=CHROME)

# ---------------------------------------------------------------- 백미러
for nm, (x, y) in (('offside', (-HX - 0.22, YF + 0.25)), ('nearside', (HX + 0.25, BULKHEAD - 0.6))):
    s = 1 if x > 0 else -1
    base = (s * HX, y + 0.15, 2.0) if s < 0 else (s * (HX - 0.01), BULKHEAD - 0.02, 1.95)
    curve_tube(f'Mirror arm {nm}', [base, (x, y, 2.05), (x, y, 1.92)], radius=0.012, material=CHROME)
    plate(f'Mirror {nm}', (x, y - 0.01, 1.84), *FRONT, 0.16, 0.24, 0.06, 0.03, UNDER)

# ---------------------------------------------------------------- 바퀴
def make_wheel(name, width):
    w = width / 2
    tyre = lathe(f'{name} tyre', [(0.33, -w), (0.44, -w), (0.485, -w + 0.03), (WHEEL_R, -w + 0.07),
                                  (WHEEL_R, w - 0.07), (0.485, w - 0.03), (0.44, w), (0.33, w), (0.33, -w)],
                 material=TYRE, seg=48)
    disc = lathe(f'{name} disc', [(0, -w + 0.06), (0.34, -w + 0.06), (0.34, w - 0.02), (0.30, w - 0.01),
                                  (0.22, w - 0.05), (0, w - 0.05)], material=WHEEL, seg=36)
    hub = lathe(f'{name} hub', [(0, 0), (0.20, 0), (0.20, 0.025), (0.17, 0.05), (0.12, 0.09), (0.05, 0.11), (0, 0.112)],
                loc=(0, 0, w - 0.06), material=CHROME, seg=36)
    nut = cylinder(f'{name} nut', r=0.018, h=0.03, loc=(0, 0, 0), material=CHROME, seg=8)
    nuts = [nut]
    for i in range(10):
        a = 2 * math.pi * i / 10
        n_ = nut if i == 0 else duplicate(nut)
        n_.location = (0.25 * math.cos(a), 0.25 * math.sin(a), w - 0.055)
        nuts.append(n_)
    whl = join([tyre, disc, hub] + nuts[1:], name)
    smooth(whl)
    whl.data.set_sharp_from_angle(angle=math.radians(40))
    return whl


front_wheel = make_wheel('Front wheel nearside', 0.30)
rear_wheel = make_wheel('Rear dual wheel nearside', 0.58)
for whl, y, w in ((front_wheel, FRONT_AXLE, 0.30), (rear_wheel, REAR_AXLE, 0.58)):
    xo = HX - 0.035 - w / 2
    whl.location = (xo, y, WHEEL_R)
    whl.rotation_euler = (0, math.pi / 2, 0)
    duplicate(whl, loc=(-xo, y, WHEEL_R), rot=(0, -math.pi / 2, 0),
              name=whl.name.replace('nearside', 'offside'))
    cylinder(f'Axle {"front" if w < 0.4 else "rear"}', r=0.07, h=2 * xo - 0.1, loc=(0, y, WHEEL_R),
             rot=(0, math.pi / 2, 0), base=False, material=UNDER, seg=16)

# 위층 앞창 환기창 가로대와 지붕 앞 환기구
for i, (x0, x1) in enumerate(((-0.86, -0.045), (0.045, 0.86))):
    end_pane(f'Upper front window vent bar {i + 1}', -1, YF + 0.02, x0 + 0.03, x1 - 0.03, 3.38, 3.41,
             mat_=CHROME, r=0.01, t=0.012)
end_pane('Front roof ventilator', -1, YF + 0.03, -0.55, 0.55, 3.64, 3.72, mat_=UNDER, r=0.03, t=0.02)

# 방향지시등(호박색)과 운전석 문 손잡이
AMBER = mat('Indicator amber', '#d98a1c', rough=0.2, emit='#e0901f', emit_strength=0.4)
for nm, x in (('nearside', 0.92), ('offside', -0.92)):
    end_pane(f'Front indicator {nm}', -1, YF + 0.03, x - 0.06, x + 0.06, 1.13, 1.20, mat_=AMBER, r=0.03, t=0.02)
for side in (1, -1):
    tag = 'nearside' if side > 0 else 'offside'
    side_pane(f'Side indicator front {tag}', side, BULKHEAD + 0.02, BULKHEAD + 0.12, 1.10, 1.17, x=HX + 0.004,
              mat_=AMBER, r=0.03, t=0.012)
end_pane('Rear indicator offside', 1, YR - 0.01, -0.92, -0.78, 1.24, 1.32, mat_=AMBER, r=0.03, t=0.04)
side_pane('Cab door handle', -1, BULKHEAD - 0.32, BULKHEAD - 0.18, 1.32, 1.35, x=HX + 0.01, mat_=CHROME, r=0.01, t=0.02)
side_pane('Cab door seam front', -1, YF + 0.16, YF + 0.175, 1.20, 2.14, x=HX + 0.002, mat_=UNDER, r=0.003, t=0.004)
side_pane('Cab door seam rear', -1, BULKHEAD - 0.115, BULKHEAD - 0.10, 1.20, 2.14, x=HX + 0.002, mat_=UNDER, r=0.003, t=0.004)
