# 제임스 웹 우주망원경(JWST) — 완전히 펼친 관측 상태.
# 주경: 평평한 윗변을 가진 육각 세그먼트 18장을 열(column) 배열로 놓고, 각 장의 앞면 꼭짓점을
#       곡률반경 15.9m 오목 구면 위로 옮겨 한 장의 큰 오목거울처럼 보이게 했다(bmesh 직접 생성).
# 차양막: 길쭉한 육각 연(kite) 모양 5겹. 가장자리는 장력 때문에 안쪽으로 오목하게 휘고,
#        겹 사이 간격은 중심에서 가장자리로 갈수록 벌어지게 극좌표 격자로 만들었다.
# 그 밖에 부경과 삼각 지지대, 후방 광학 배플, 검은 백플레인과 ISIM 상자, 전개 타워,
# 우주선 버스·태양전지판·고이득 안테나·모멘텀 플랩을 실제 치수(m)에 맞춰 단순화해 붙였다.
from atelier import *

SHOT = {"azimuth": -64, "elevation": 21, "lens": 45, "light": "studio", "background": "#161b2b"}

# ── 재질 ────────────────────────────────────────────────
GOLD = mat('Gold_Beryllium_Mirror', '#e8b852', rough=0.12, metal=1.0)
BLACK = mat('Black_Composite', '#24252b', rough=0.6, metal=0.1)
STRUT = mat('Graphite_Strut', '#3a3c42', rough=0.45, metal=0.3)
KAPTON_SUN = mat('Kapton_Sunshield_Purple', '#b9a2cf', rough=0.28, metal=0.75)
KAPTON = mat('Kapton_Sunshield_Silver', '#d6cddf', rough=0.18, metal=0.85)
MLI = mat('Gold_MLI_Blanket', '#c48d34', rough=0.42, metal=0.7)
SILVER = mat('Radiator_Silver', '#b9bdc4', rough=0.32, metal=0.75)
GREY = mat('Bus_Grey', '#7d828a', rough=0.45, metal=0.5)
SOLAR = mat('Solar_Cells', '#1f2c4e', rough=0.22, metal=0.4)
DISH = mat('Antenna_White', '#dcd8cc', rough=0.5, metal=0.0)


def tube(name, a, b, r, material, seg=10):
    """두 점 a→b 를 잇는 원통."""
    a, b = Vector(a), Vector(b)
    d = b - a
    ob = cylinder(name, r=r, h=d.length, material=material, seg=seg, base=True)
    ob.location = a
    ob.rotation_euler = d.to_track_quat('Z', 'Y').to_euler()
    return ob


# ── 차양막(Sunshield) 5겹 ────────────────────────────────
SS_HALF_L, SS_HALF_W, SS_TIP = 10.6, 7.08, 1.7


def ss_corners():
    return [(SS_TIP, -SS_HALF_L), (SS_HALF_W, 0.0), (SS_TIP, SS_HALF_L),
            (-SS_TIP, SS_HALF_L), (-SS_HALF_W, 0.0), (-SS_TIP, -SS_HALF_L)]


def ss_outline(per_edge=24, sag=0.32):
    """모서리 사이를 안쪽으로 오목하게 휜 외곽선(반시계). 짧은 끝변은 덜 휜다."""
    cs = ss_corners()
    pts = []
    for i in range(6):
        p, q = Vector(cs[i]), Vector(cs[(i + 1) % 6])
        e = q - p
        n = Vector((-e.y, e.x)).normalized()        # 반시계 외곽의 안쪽 법선
        s_amt = sag * min(1.0, e.length / 8.0)
        for k in range(per_edge):
            s = k / per_edge
            pts.append(p + e * s + n * (s_amt * math.sin(math.pi * s)))
    return pts


def layer_z(i, t):
    return i * (0.07 + 0.19 * t ** 1.3)


def sunshield_layer(i, scale, material):
    outline = ss_outline()
    rings = 20
    bm = bmesh.new()
    c = bm.verts.new((0, 0, layer_z(i, 0)))
    grid = []
    for k in range(1, rings + 1):
        t = k / rings
        row = []
        for p in outline:
            phi = math.atan2(p.y, p.x)
            # 막이 팽팽히 당겨지며 생기는 잔주름(가장자리로 갈수록 커진다)
            w = 0.03 * t * math.sin(11 * phi + 1.7 * i) * math.sin(math.pi * 2.5 * t)
            row.append(bm.verts.new((p.x * t * scale, p.y * t * scale, layer_z(i, t) + w)))
        grid.append(row)
    n = len(outline)
    for j in range(n):
        bm.faces.new((c, grid[0][j], grid[0][(j + 1) % n]))
    for k in range(rings - 1):
        a, b = grid[k], grid[k + 1]
        for j in range(n):
            bm.faces.new((a[j], b[j], b[(j + 1) % n], a[(j + 1) % n]))
    me = bpy.data.meshes.new(f'Sunshield_Layer_{i + 1}')
    bm.to_mesh(me)
    bm.free()
    ob = link(bpy.data.objects.new(f'Sunshield_Layer_{i + 1}', me))
    assign(ob, material)
    solidify(ob, 0.02)
    smooth(ob)
    return ob


LAYER_SCALE = [1.0 - 0.02 * i for i in range(5)]
for i in range(5):
    sunshield_layer(i, LAYER_SCALE[i], KAPTON_SUN if i < 2 else KAPTON)

# 모서리 스프레더 바: 각 겹의 모서리를 위아래로 꿴다
for ci, (cx, cy) in enumerate(ss_corners()):
    pts = [(cx * LAYER_SCALE[i], cy * LAYER_SCALE[i], layer_z(i, 1.0)) for i in range(5)]
    pts = [(pts[0][0], pts[0][1], -0.05)] + pts + [(pts[-1][0], pts[-1][1], pts[-1][2] + 0.06)]
    curve_tube(f'Spreader_Bar_{ci + 1}', pts, radius=0.045, material=STRUT)

# 미드붐(좌우)과 앞뒤 팔레트 빔(맨 아래 겹 밑)
for sx in (-1, 1):
    pts = []
    for k in range(0, 11):
        t = k / 10
        pts.append((sx * SS_HALF_W * LAYER_SCALE[2] * t, 0.0, layer_z(2, t) + 0.04))
    curve_tube(f'Mid_Boom_{"L" if sx < 0 else "R"}', pts, radius=0.06, material=STRUT)
for sy in (-1, 1):
    curve_tube(f'Pallet_Beam_{"Fwd" if sy < 0 else "Aft"}', [(0, 0, -0.1), (0, sy * (SS_HALF_L - 0.1), -0.1)],
               radius=0.08, material=STRUT)

# ── 우주선 버스(차양막 아래, 태양을 향하는 쪽) ───────────
BUS_Y = 0.4
bus = box('Spacecraft_Bus', (2.4, 2.4, 1.3), loc=(0, BUS_Y, -1.32), material=MLI, base=True)
bevel(bus, 0.04, 2)
box('Bus_Bottom_Deck', (2.5, 2.5, 0.08), loc=(0, BUS_Y, -1.38), material=GREY, base=True)

# 태양전지판: 버스 뒷면 아래 모서리에서 뒤로 비스듬히
sa_hinge = Vector((0, BUS_Y + 1.25, -1.2))
sa = box('Solar_Array', (2.5, 4.4, 0.06), loc=(0, 2.2, 0), material=SOLAR)
sa_frame = box('Solar_Array_Frame', (2.6, 4.5, 0.04), loc=(0, 2.2, 0.04), material=GREY)
sa_root = bpy.data.objects.new('Solar_Array_Hinge', None)
link(sa_root)
sa_root.location = sa_hinge
sa_root.rotation_euler = (math.radians(-14), 0, 0)
parent_to([sa, sa_frame], sa_root)

# 고이득 안테나: 버스 앞쪽 밑에 매달린 접시
hga_root = Vector((0.6, BUS_Y - 0.9, -1.38))
tube('HGA_Boom', hga_root, hga_root + Vector((0, -0.2, -0.9)), 0.05, GREY)
dish = lathe('HGA_Dish', [(0.0, 0.0), (0.2, 0.012), (0.4, 0.05), (0.58, 0.11), (0.6, 0.13)], material=DISH, seg=40)
solidify(dish, 0.02)
dish.location = hga_root + Vector((0, -0.2, -0.88))
dish.rotation_euler = (math.radians(200), 0, 0)

# 모멘텀 트림 플랩: 뒤쪽 끝
flap_pts = [(-1.5, 0), (1.5, 0), (0.9, 1.7), (-0.9, 1.7)]
flap = prism('Momentum_Trim_Flap', flap_pts, 0.04, material=KAPTON)
flap.location = (0, SS_HALF_L, 0.03)
flap.rotation_euler = (math.radians(32), 0, 0)
tube('Flap_Hinge', (-SS_TIP, SS_HALF_L, 0.05), (SS_TIP, SS_HALF_L, 0.05), 0.05, STRUT)

# 버스 옆면 방열판과 별 추적기
for sx in (-1, 1):
    box(f'Bus_Radiator_{"L" if sx < 0 else "R"}', (0.04, 1.9, 0.9), loc=(sx * 1.22, BUS_Y, -1.12), material=SILVER, base=True)
for i, ang in enumerate((-30, 30)):
    st = cylinder(f'Star_Tracker_{i + 1}', r=0.13, r2=0.17, h=0.4, material=GREY, seg=20, base=True)
    st.location = (ang / 40, BUS_Y - 1.15, -1.1)
    st.rotation_euler = (math.radians(120), 0, math.radians(ang))

# ── 광학 망원경 요소(OTE) ─ 로컬 좌표: 거울 정점 원점, -Y 가 관측 방향 ──
ote = bpy.data.objects.new('Optical_Telescope_Element', None)
link(ote)
OTE_TILT = math.radians(-9)
ote.location = (0, -0.9, 4.55)
ote.rotation_euler = (OTE_TILT, 0, 0)
ote_parts = []

RC = 15.88            # 주경 곡률반경
R0 = 1.32 / math.sqrt(3)   # 세그먼트 피치(외접반지름)
RG = R0 - 0.012       # 틈을 둔 실제 외접반지름
H = math.sqrt(3) * R0
SEG_T = 0.06


def sag(u, v):
    return -(u * u + v * v) / (2 * RC)


def seg_centers():
    out = []
    for c in range(-2, 3):
        u = c * 1.5 * R0
        if c == 0:
            ks = [k * H for k in (-2, -1, 1, 2)]
        elif abs(c) == 2:
            ks = [k * H for k in (-1, 0, 1)]
        else:
            ks = [(k + 0.5) * H for k in (-2, -1, 0, 1)]
        out += [(u, v) for v in ks]
    return out


def hex_ring(cu, cv, r, sub=2):
    pts = []
    for i in range(6):
        a0, a1 = math.radians(60 * i), math.radians(60 * (i + 1))
        p0 = Vector((math.cos(a0), math.sin(a0))) * r
        p1 = Vector((math.cos(a1), math.sin(a1))) * r
        for s in range(sub):
            p = p0.lerp(p1, s / sub)
            pts.append((cu + p.x, cv + p.y))
    return pts


def build_segments(name, radius, y_off, depth, material, rings=3, smooth_front=True):
    bm = bmesh.new()
    for cu, cv in seg_centers():
        c = bm.verts.new((cu, sag(cu, cv) + y_off, cv))
        front = []
        for k in range(1, rings + 1):
            ring = hex_ring(cu, cv, radius * k / rings, sub=2 * k)
            front.append([bm.verts.new((u, sag(u, v) + y_off, v)) for u, v in ring])
        outer = front[-1]
        back = [bm.verts.new((v.co.x, v.co.y + depth, v.co.z)) for v in outer]
        bc = bm.verts.new((cu, sag(cu, cv) + y_off + depth, cv))
        n0 = len(front[0])
        for j in range(n0):
            f = bm.faces.new((c, front[0][(j + 1) % n0], front[0][j]))
            f.smooth = smooth_front
        for k in range(rings - 1):
            a, b = front[k], front[k + 1]
            na, nb = len(a), len(b)
            # 바깥 고리는 꼭짓점 수가 다르다: 육각 변마다 삼각형 띠로 잇는다
            sa_, sb_ = na // 6, nb // 6
            for e in range(6):
                ia = [e * sa_ + s for s in range(sa_ + 1)]
                ib = [e * sb_ + s for s in range(sb_ + 1)]
                i, j = 0, 0
                while i < sa_ or j < sb_:
                    if j < sb_ and (i >= sa_ or (j + 1) / sb_ <= (i + 1) / sa_):
                        f = bm.faces.new((a[ia[i] % na], b[ib[j + 1] % nb], b[ib[j] % nb]))
                        j += 1
                    else:
                        f = bm.faces.new((a[ia[i] % na], a[ia[i + 1] % na], b[ib[j] % nb]))
                        i += 1
                    f.smooth = smooth_front
        no = len(outer)
        for j in range(no):
            bm.faces.new((outer[j], outer[(j + 1) % no], back[(j + 1) % no], back[j]))
            bm.faces.new((bc, back[j], back[(j + 1) % no]))
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
    me = bpy.data.meshes.new(name)
    bm.to_mesh(me)
    bm.free()
    ob = link(bpy.data.objects.new(name, me))
    return assign(ob, material)


ote_parts.append(build_segments('Primary_Mirror_18_Segments', RG, 0.0, SEG_T, GOLD, rings=3))
# 세그먼트 뒤의 검은 백플레인(세그먼트보다 살짝 큰 육각 블록이 이어져 한 판처럼 보인다)
ote_parts.append(build_segments('Primary_Backplane', R0 + 0.004, SEG_T + 0.005, 0.38, BLACK, rings=1, smooth_front=False))

# 중앙 구멍 속 후방 광학 배플(AOS)
aos = cylinder('Aft_Optics_Baffle', r=0.42, r2=0.34, h=1.25, material=BLACK, seg=24, base=True)
aos.location = (0, 0.35, 0)
aos.rotation_euler = (math.radians(90), 0, 0)
ote_parts.append(aos)
aos_lip = torus('Aft_Optics_Lip', R=0.35, r=0.03, material=STRUT, seg=32, rseg=8)
aos_lip.location = (0, -0.9, 0)
aos_lip.rotation_euler = (math.radians(90), 0, 0)
ote_parts.append(aos_lip)

# 부경과 삼각 지지대
SM_Y = -7.17
sm = cylinder('Secondary_Mirror', r=0.37, h=0.05, material=GOLD, seg=36, base=True)
sm.location = (0, SM_Y + 0.05, 0)
sm.rotation_euler = (math.radians(90), 0, 0)
sm_cell = cylinder('Secondary_Mirror_Cell', r=0.42, r2=0.3, h=0.35, material=BLACK, seg=36, base=True)
sm_cell.location = (0, SM_Y, 0)
sm_cell.rotation_euler = (math.radians(-90), 0, 0)
ote_parts += [sm, sm_cell]
top_v = 2 * H + R0 * math.sqrt(3) / 2
strut_feet = [(0.0, sag(0, top_v) + 0.1, top_v - 0.05),
              (2.55, sag(2.55, -1.75) + 0.1, -1.75), (-2.55, sag(2.55, -1.75) + 0.1, -1.75)]
strut_heads = [(0.0, SM_Y - 0.3, 0.38), (0.33, SM_Y - 0.3, -0.2), (-0.33, SM_Y - 0.3, -0.2)]
for i, (a, b) in enumerate(zip(strut_feet, strut_heads)):
    ote_parts.append(tube(f'SM_Support_Strut_{i + 1}', a, b, 0.055, STRUT))
    ote_parts.append(sphere(f'SM_Strut_Hinge_{i + 1}', 0.1, loc=a, material=STRUT, seg=12, rings=8))

# 백플레인 뒤: ISIM 상자, 방열판, 지지 트러스
isim = box('ISIM_Enclosure', (2.0, 1.6, 2.6), loc=(0, 1.25, -1.0), material=BLACK)
bevel(isim, 0.03, 1)
rad = box('ISIM_Radiator', (2.4, 0.08, 3.2), loc=(0, 2.1, -0.6), material=SILVER)
ote_parts += [isim, rad]
for sx in (-1, 1):
    ote_parts.append(tube(f'Backplane_Support_Truss_{sx}', (sx * 1.9, 0.45, -2.6), (sx * 0.9, 1.9, -2.2), 0.05, STRUT))
    ote_parts.append(tube(f'Backplane_Support_Truss_Up_{sx}', (sx * 1.9, 0.45, 2.0), (sx * 0.9, 2.05, 0.9), 0.05, STRUT))
parent_to(ote_parts, ote)

# 전개 타워: ISIM 아래에서 버스 위까지
bpy.context.view_layer.update()
tower_top = ote.matrix_world @ Vector((0, 1.3, -2.25))
tube('Deployable_Tower', (tower_top.x, tower_top.y, -0.05), (tower_top.x, tower_top.y, tower_top.z + 0.05), 0.28, MLI, seg=20)
