본문으로 건너뛰기
3D 아틀리에
허블 우주망원경 3D 모형

끌어서 회전, 휠·두 손가락으로 확대

이 모형에 대하여

허블 우주망원경은 미국 항공우주국(NASA)과 유럽우주국(ESA)이 함께 운용하는 우주망원경이다. 1990년 4월 우주왕복선 디스커버리호(STS-31)에 실려 지구 저궤도에 올랐다. 대기의 흔들림과 흡수가 없는 궤도에서 지름 2.4m 주경으로 자외선부터 근적외선까지 관측해 왔다.

발사 직후 주경의 구면수차가 발견되었고, 1993년 첫 정비 임무에서 보정 광학계를 설치해 바로잡았다. 이후 2009년까지 모두 다섯 차례 우주비행사들이 우주왕복선으로 찾아가 관측 장비와 자이로스코프, 태양전지판, 배터리를 교체했다. 우주 팽창 속도 측정과 '허블 딥 필드' 같은 관측으로 천문학의 여러 분야에 기여했다.

이 모델은 마지막 정비(2009년) 이후의 모습을 실제 크기로 재현했다. 은박 단열재로 싸인 원통형 몸체, 앞쪽의 열린 개구부 덮개, 몸통 가운데 장비 구획에서 양옆으로 펼친 강체형 태양전지판(SA3), 위아래 붐 끝의 고이득 안테나 두 기, 몸통 곳곳의 노란 손잡이를 표현했다. 우주에 떠 있는 물체이므로 전시용 받침대 위에 올려 두었다.

만든 방법

몸통은 망원경 축을 따라 단면을 돌린 회전체(lathe)로 후방 덮개, 10각형 장비 구획, 전방 차광통을 차례로 쌓았다. 은박 단열재의 구김은 고정 시드 난수로 원통 반지름을 수 밀리미터씩 흔들어 반사광이 고르지 않게 나오도록 했다. 태양전지판은 셀 격자를 bmesh 루프로 한 장 만들고 링크 복제로 네 장을 붙였으며, 개구부 덮개와 전지판의 기울기는 변환 행렬로 배치했다. 고이득 안테나는 포물선 단면을 회전한 뒤 두께를 주었고, 손잡이는 커브 튜브로 만들었다. 미세 유도 센서·관측 장비의 실제 문 배치, 단열재 덧댐의 정확한 위치, 내부 광학계는 단순화했다.

build.py

이 파일 하나를 Blender 에서 실행하면 위의 모형이 만들어집니다. 313줄.

# 허블 우주망원경 (Hubble Space Telescope) — 2009년 마지막 정비(SM4) 이후의 궤도 위 모습.
# 망원경 축을 X 축(+X 가 개구부)으로 눕혀 실제 크기(길이 약 13.2m, 후방 지름 4.2m)로 만들었다.
# 몸통은 회전체(lathe)로 후방 덮개·장비 구획(10각 베이)·전방 차광통을 쌓고, 차광통 안쪽은 어두운 내벽과
# 주경·부경 스파이더까지 넣었다. 태양전지판(SA3)은 셀 격자를 bmesh 루프로 한 장 만든 뒤
# 링크 복제로 네 장을 붙였고, 열린 개구부 덮개·고이득 안테나(포물면 회전체)·노란 손잡이(커브 튜브)를 더했다.
# 은박 단열재(MLI)와 흰 NOBL 덮개는 Principled 상수 재질로 구분했다. 전시용 받침대 위에 올려 둔다.
from atelier import *
import random
random.seed(1990)
SHOT = {"azimuth": -38, "elevation": 22, "lens": 50, "light": "studio", "background": "#1c2433"}
H = 5.6 # 망원경 축 높이(받침대 위)
R_AFT = 2.10 # 후방 덮개 반지름
R_ES = 2.24 # 장비 구획(10각) 외접 반지름
R_LS = 1.53 # 전방 셸·차광통 반지름
X_ES0, X_ES1 = 3.50, 5.00
X_FRONT = 13.05
M_MLI = mat('MLI_Silver_Foil', '#c8ccd2', rough=0.28, metal=1.0)
M_AFT = mat('Aft_Shroud_Aluminized', '#aeb3ba', rough=0.38, metal=0.9)
M_NOBL = mat('NOBL_White_Blanket', '#dedcd4', rough=0.62)
M_DARK = mat('Baffle_Black', '#16171a', rough=0.85)
M_MECH = mat('Mechanism_Gray', '#4a4e55', rough=0.45, metal=0.6)
M_CELL = mat('Solar_Cell_Blue', '#1a2f66', rough=0.2, metal=0.3)
M_FRAME = mat('Solar_Frame_Gold', '#b88d43', rough=0.35, metal=0.85)
M_RAIL = mat('Handrail_Yellow', '#d8a823', rough=0.5)
M_DISH = mat('HGA_Dish_White', '#d6d3ca', rough=0.55)
M_DOOR = mat('Aperture_Door_Coating', '#c3c6cb', rough=0.45, metal=0.35)
M_MIRROR = mat('Primary_Mirror', '#8fa3b8', rough=0.08, metal=1.0)
M_STAND = mat('Display_Stand_Graphite', '#2b2e34', rough=0.4, metal=0.5)
AX = (0, math.pi / 2, 0) # lathe 의 Z 축 → 월드 +X
def P(x, r, th):
"""망원경 축 좌표(x, 반지름, 각도θ) → 월드 좌표. θ=0 은 +Y, 90° 는 위(+Z)."""
return Vector((x, r * math.cos(th), H + r * math.sin(th)))
def axial(name, profile, x0, material, seg=64, sm=True):
return lathe(name, profile, loc=(x0, 0, H), rot=AX, material=material, seg=seg, shade_smooth=sm)
def shell_patch(name, r, t, a0, a1, x0, x1, material, n=10):
"""원통 표면에 붙는 곡면 패널(두께 t)."""
bm = bmesh.new()
나머지 265줄 펼치기
vs = []
for rr in (r, r + t):
ring = []
for i in range(n + 1):
a = a0 + (a1 - a0) * i / n
ring.append((bm.verts.new(P(x0, rr, a)), bm.verts.new(P(x1, rr, a))))
vs.append(ring)
inn, out = vs
for i in range(n):
bm.faces.new((out[i][0], out[i + 1][0], out[i + 1][1], out[i][1]))
bm.faces.new((inn[i][1], inn[i + 1][1], inn[i + 1][0], inn[i][0]))
bm.faces.new((inn[i][0], inn[i + 1][0], out[i + 1][0], out[i][0]))
bm.faces.new((out[i][1], out[i + 1][1], inn[i + 1][1], inn[i][1]))
bm.faces.new((inn[0][0], out[0][0], out[0][1], inn[0][1]))
bm.faces.new((inn[n][1], out[n][1], out[n][0], inn[n][0]))
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)
smooth(ob)
return assign(ob, material)
def rail(name, x0, x1, r, th, h=0.13, rad=0.022):
"""축 방향 손잡이: 양 끝 받침이 있는 ㄷ자 관."""
pts = [P(x0, r - 0.01, th), P(x0, r + h, th), P(x1, r + h, th), P(x1, r - 0.01, th)]
return curve_tube(name, [tuple(p) for p in pts], radius=rad, material=M_RAIL, resolution=2, bevel_res=2)
def ring_band(name, x, r, w, t, material, seg=64):
return axial(name, [(r - 0.01, -w / 2), (r + t, -w / 2), (r + t, w / 2), (r - 0.01, w / 2)], x, material, seg)
def crinkled_tube(name, r, x0, x1, material, rows, seg=96, amp=0.008, lip=None):
"""은박 단열재의 구김: 반지름을 고정 시드 난수로 조금씩 흔든 원통."""
prof = []
for i in range(rows + 1):
x = x0 + (x1 - x0) * i / rows
prof.append((r, x))
if lip:
prof.append((r - lip, x1))
ob = axial(name, prof, 0.0, material, seg=seg)
for v in ob.data.vertices:
rr = math.hypot(v.co.x, v.co.y)
if rr > r - 0.001 and x0 + 0.05 < v.co.z < x1 - 0.05:
k = 1 + random.uniform(-amp, amp) / r
v.co.x *= k
v.co.y *= k
return ob
# ---------------------------------------------------------------- 1. 몸통
# 후방 덮개(Aft Shroud) + 후방 격벽
axial('Aft_Shroud_Bulkhead_Edge', [(0.0, 0.0), (1.70, 0.0), (1.95, 0.05), (R_AFT, 0.20), (R_AFT, 0.30)], 0.0, M_AFT, seg=96)
crinkled_tube('Aft_Shroud', R_AFT, 0.30, X_ES0, M_AFT, rows=30, amp=0.005)
axial('Aft_Bulkhead_Plate', [(0.0, -0.03), (1.60, -0.03), (1.60, 0.01), (0.0, 0.01)], 0.0, M_MECH, sm=False)
for k in range(8):
th = math.radians(22.5 + 45 * k)
rib = box(f'Aft_Bulkhead_Rib_{k}', (0.06, 1.15, 0.10), loc=P(-0.06, 0.95, th), material=M_AFT)
rib.rotation_euler = (th, 0, 0)
axial('Aft_Bulkhead_Center_Port', [(0.0, -0.10), (0.38, -0.10), (0.42, -0.03), (0.42, 0.0)], 0.0, M_AFT, seg=32)
ring_band('Aft_Shroud_Flange', 0.22, R_AFT, 0.08, 0.035, M_MECH)
ring_band('Aft_Shroud_Ring_Mid', 1.80, R_AFT, 0.05, 0.02, M_MECH)
ring_band('Aft_Shroud_Forward_Ring', X_ES0 - 0.04, R_AFT, 0.08, 0.06, M_MECH)
# 장비 구획(SSM Equipment Section): 10개 베이의 다각형 링
axial('Equipment_Section', [(R_AFT - 0.05, 0.0), (R_ES, 0.0), (R_ES, X_ES1 - X_ES0), (R_LS - 0.02, X_ES1 - X_ES0)],
X_ES0, M_MLI, seg=10, sm=False)
# 전방 셸 + 차광통(Light Shield): 바깥 은박 + 앞 테두리
crinkled_tube('Forward_Shell_Light_Shield', R_LS, X_ES1 - 0.01, X_FRONT, M_MLI, rows=80, lip=0.07)
axial('Light_Shield_Inner_Baffle', [(R_LS - 0.07, X_FRONT), (R_LS - 0.07, 5.6), (0.0, 5.6)], 0.0, M_DARK)
ring_band('Forward_Shell_Joint_Ring', 9.0, R_LS, 0.10, 0.03, M_MECH)
for i, x in enumerate((6.0, 7.0, 8.0, 10.0, 11.0, 12.0)):
ring_band(f'Light_Shield_Seam_{i}', x, R_LS, 0.035, 0.012, M_AFT)
ring_band('Light_Shield_Front_Lip', X_FRONT - 0.06, R_LS, 0.12, 0.04, M_MECH)
# 차광통 안: 주경(가운데 구멍), 내부 차광 링, 부경과 4갈래 스파이더
axial('Primary_Mirror', [(0.32, 5.62), (1.22, 5.62), (1.22, 5.70), (0.32, 5.70)], 0.0, M_MIRROR, sm=False)
for i, x in enumerate((7.4, 9.2, 11.0)):
axial(f'Internal_Baffle_Ring_{i}', [(R_LS - 0.07, x), (R_LS - 0.22, x + 0.02), (R_LS - 0.22, x + 0.05), (R_LS - 0.07, x + 0.05)],
0.0, M_DARK, sm=False)
axial('Secondary_Mirror_Housing', [(0.0, 10.15), (0.20, 10.15), (0.20, 10.55), (0.05, 10.62), (0.0, 10.62)], 0.0, M_MECH)
for k in range(4):
th = math.radians(45 + 90 * k)
a, b = P(10.38, 0.18, th), P(10.38, R_LS - 0.08, th)
mid, d = (a + b) / 2, (b - a)
vane = box(f'Spider_Vane_{k}', (0.30, d.length, 0.018), loc=mid, material=M_DARK)
vane.rotation_euler = (th, 0, 0)
# ---------------------------------------------------------------- 2. 장비 베이 문(10개)
bay_w = 2 * R_ES * math.sin(math.radians(18)) * 0.86
apo = R_ES * math.cos(math.radians(18))
bay_mats = [None, M_NOBL, M_NOBL, M_MLI, M_NOBL, None, M_NOBL, M_MLI, M_NOBL, M_NOBL]
for k in range(10):
th = math.radians(36 * k)
c = P((X_ES0 + X_ES1) / 2, apo + 0.025, th)
if bay_mats[k] is None:
# 태양전지판 뿌리 부분: 구동 장치 상자
d = box(f'SA_Drive_Housing_{k}', (1.25, 0.55, 0.10), loc=c, material=M_MECH)
else:
d = box(f'Equipment_Bay_Door_{k}', (1.30, bay_w, 0.05), loc=c, material=bay_mats[k])
rail(f'Bay_Handrail_{k}', X_ES0 + 0.25, X_ES1 - 0.25, apo + 0.05, th + math.radians(10.5), h=0.10)
d.rotation_euler = (th - math.pi / 2, 0, 0)
# ---------------------------------------------------------------- 3. 후방 덮개 문·손잡이
for k in range(4):
th0 = math.radians(45 + 90 * k)
shell_patch(f'Aft_Shroud_Door_{k}', R_AFT, 0.03, th0 - math.radians(28), th0 + math.radians(28), 0.55, 3.15, M_MLI)
for s in (-1, 1):
rail(f'Aft_Door_Handrail_{k}_{"L" if s < 0 else "R"}', 0.75, 2.95, R_AFT + 0.03, th0 + s * math.radians(33))
for k in range(4):
th = math.radians(90 * k)
rail(f'Aft_Shroud_Handrail_{k}', 0.40, 3.30, R_AFT, th)
# 후방 격벽 저이득 안테나
cylinder('Aft_LGA_Mast', 0.05, 0.55, loc=(0.0, 0, H + 1.1), rot=(0, -math.pi / 2, 0), material=M_MECH, seg=12)
cone('Aft_Low_Gain_Antenna', 0.16, 0.30, loc=(-0.55, 0, H + 1.1), rot=(0, -math.pi / 2, 0), material=M_DISH, seg=20)
# ---------------------------------------------------------------- 4. 전방 셸·차광통 외부 부품
# 자기 토커 막대 4개(NOBL 덮개)
for k in range(4):
th = math.radians(45 + 90 * k)
c = P(10.9, R_LS + 0.13, th)
bar = cylinder(f'Magnetic_Torquer_{k}', 0.07, 3.2, loc=c, rot=(0, math.pi / 2, 0), material=M_NOBL, seg=16, base=False)
for j, x in enumerate((9.5, 12.3)):
st = box(f'Magnetic_Torquer_{k}_Standoff_{j}', (0.10, 0.08, 0.16), loc=P(x, R_LS + 0.06, th), material=M_MECH)
st.rotation_euler = (th - math.pi / 2, 0, 0)
# 차광통 손잡이
for k, th in enumerate((0.0, math.pi)):
rail(f'Light_Shield_Handrail_{k}', 6.2, 12.6, R_LS, th)
rail(f'Forward_Shell_Handrail_{k}', 5.3, 8.6, R_LS, th + math.radians(25))
# 태양 센서·고정 별추적기 상자
for k, (x, th) in enumerate(((12.4, math.radians(-30)), (12.4, math.radians(210)), (5.6, math.radians(-125)))):
s = box(f'Sun_Sensor_{k}', (0.35, 0.28, 0.18), loc=P(x, R_LS + 0.08, th), material=M_MECH)
s.rotation_euler = (th - math.pi / 2, 0, 0)
# 전방 저이득 안테나
cylinder('Forward_LGA_Mast', 0.04, 0.9, loc=P(11.6, R_LS - 0.02, math.radians(-90)), rot=(math.pi, 0, 0), material=M_MECH, seg=10)
cone('Forward_Low_Gain_Antenna', 0.14, 0.26, loc=P(11.6, R_LS + 0.86, math.radians(-90)), rot=(math.pi, 0, 0), material=M_DISH, seg=20)
# ---------------------------------------------------------------- 5. 개구부 덮개(열린 상태)
HINGE = Vector((X_FRONT + 0.05, 0, H + R_LS + 0.10))
R_DOOR = 1.62
OPEN = math.radians(100)
door_m = Matrix.Translation(HINGE) @ Matrix.Rotation(-OPEN, 4, 'Y') @ Matrix.Translation((0, 0, -R_DOOR - 0.0)) @ Matrix.Rotation(math.pi / 2, 4, 'Y')
door = cylinder('Aperture_Door', R_DOOR, 0.10, material=M_DOOR, seg=64, base=False)
door.matrix_world = door_m @ Matrix.Translation((0, 0, 0.03))
door_in = cylinder('Aperture_Door_Inner_Black', 1.50, 0.02, material=M_DARK, seg=64, base=False)
door_in.matrix_world = door_m @ Matrix.Translation((0, 0, -0.03))
door_rib = lathe('Aperture_Door_Rim', [(R_DOOR - 0.06, 0.07), (R_DOOR + 0.02, 0.07), (R_DOOR + 0.02, 0.11), (R_DOOR - 0.06, 0.11)],
material=M_MECH, seg=64, shade_smooth=False)
door_rib.matrix_world = door_m
for k in range(3):
st = box(f'Aperture_Door_Stiffener_{k}', (0.06, 2 * R_DOOR * 0.92 * math.cos(math.radians(30 * (k - 1))) * 0.9, 0.04), material=M_MECH)
st.matrix_world = door_m @ Matrix.Translation((R_DOOR * 0.92 * math.sin(math.radians(30 * (k - 1))) * 0.9, 0, 0.10))
cylinder('Aperture_Door_Hinge', 0.08, 1.1, loc=HINGE, rot=(math.pi / 2, 0, 0), material=M_MECH, seg=16, base=False)
for s in (-1, 1):
box(f'Aperture_Door_Hinge_Bracket_{"L" if s < 0 else "R"}', (0.55, 0.10, 0.16),
loc=(HINGE.x - 0.22, s * 0.42, H + R_LS + 0.03), material=M_MECH)
# 덮개 개폐 구동기
curve_tube('Aperture_Door_Actuator', [tuple(P(12.2, R_LS + 0.02, math.radians(90))), tuple(P(12.2, R_LS + 0.18, math.radians(90))),
tuple(HINGE + Vector((-0.05, 0, 0.02)))], radius=0.035, material=M_MECH, resolution=2)
# ---------------------------------------------------------------- 6. 태양전지판 (SA3 강체형) 2날개
SA_X = (X_ES0 + X_ES1) / 2
SA_TILT = math.radians(50)
SA_L, SA_W = 7.1, 1.24
SA_ROOT = 2.75
NCOL, NROW, GAP = 5, 22, 0.014
bm = bmesh.new()
bmesh.ops.create_cube(bm, size=1.0)
bmesh.ops.scale(bm, vec=(SA_W, SA_L, 0.035), verts=bm.verts)
bmesh.ops.translate(bm, vec=(SA_W / 2, SA_L / 2, 0), verts=bm.verts)
cw = (SA_W - GAP * (NCOL + 1)) / NCOL
ch = (SA_L - GAP * (NROW + 1)) / NROW
frame_me = bpy.data.meshes.new('SA_Panel_Frame')
bm.to_mesh(frame_me)
bm.free()
bm = bmesh.new()
for i in range(NCOL):
for j in range(NROW):
g = bmesh.ops.create_cube(bm, size=1.0)
bmesh.ops.scale(bm, vec=(cw, ch, 0.012), verts=g['verts'])
bmesh.ops.translate(bm, vec=(GAP + cw / 2 + i * (cw + GAP), GAP + ch / 2 + j * (ch + GAP), 0.0175 + 0.004), verts=g['verts'])
cell_me = bpy.data.meshes.new('SA_Panel_Cells')
bm.to_mesh(cell_me)
bm.free()
bm = bmesh.new()
bmesh.ops.create_cube(bm, size=1.0)
bmesh.ops.scale(bm, vec=(SA_W - 0.04, SA_L - 0.04, 0.008), verts=bm.verts)
bmesh.ops.translate(bm, vec=(SA_W / 2, SA_L / 2, -0.0175 - 0.002 - 0.004), verts=bm.verts)
back_me = bpy.data.meshes.new('SA_Panel_Back')
bm.to_mesh(back_me)
bm.free()
back_ob = assign(link(bpy.data.objects.new('SA_Panel_Back', back_me)), M_AFT)
frame_ob = assign(link(bpy.data.objects.new('SA_Panel_Frame', frame_me)), M_FRAME)
cell_ob = assign(link(bpy.data.objects.new('SA_Panel_Cells', cell_me)), M_CELL)
first = True
for side, name in ((1, 'Plus'), (-1, 'Minus')):
W = Matrix.Translation((SA_X, 0, H)) @ Matrix.Rotation(SA_TILT, 4, 'Y')
if side < 0:
W = W @ Matrix.Rotation(math.pi, 4, 'Z')
for h, off in (('A', 0.07), ('B', -0.07 - SA_W)):
L = Matrix.Translation((off, SA_ROOT, 0))
if first:
f, c, b = frame_ob, cell_ob, back_ob
f.name, c.name, b.name = f'SA_{name}_Panel_{h}_Frame', f'SA_{name}_Panel_{h}_Cells', f'SA_{name}_Panel_{h}_Back'
first = False
else:
f = duplicate(frame_ob, name=f'SA_{name}_Panel_{h}_Frame')
c = duplicate(cell_ob, name=f'SA_{name}_Panel_{h}_Cells')
b = duplicate(back_ob, name=f'SA_{name}_Panel_{h}_Back')
f.matrix_world = W @ L
c.matrix_world = W @ L
b.matrix_world = W @ L
mast = cylinder(f'SA_{name}_Mast', 0.06, SA_ROOT + SA_L + 0.15 - 2.05, material=M_MECH, seg=12)
mast.matrix_world = W @ Matrix.Translation((0, 2.05, 0)) @ Matrix.Rotation(-math.pi / 2, 4, 'X')
for j, u in enumerate((SA_ROOT, SA_ROOT + SA_L)):
beam = box(f'SA_{name}_Cross_Beam_{j}', (2 * SA_W + 0.18, 0.08, 0.07), material=M_FRAME)
beam.matrix_world = W @ Matrix.Translation((0, u, 0))
hub = cylinder(f'SA_{name}_Deploy_Mechanism', 0.16, 0.45, material=M_MECH, seg=16)
hub.matrix_world = W @ Matrix.Translation((0, 2.15, 0)) @ Matrix.Rotation(-math.pi / 2, 4, 'X')
# ---------------------------------------------------------------- 7. 고이득 안테나(HGA) 2기
HGA_X = 7.7
BOOM = 2.5
f_len, r_dish = 0.48, 0.66
prof = [(r_dish * i / 10, (r_dish * i / 10) ** 2 / (4 * f_len)) for i in range(11)]
dish_src = lathe('HGA_Dish', prof, material=M_DISH, seg=40)
solidify(dish_src, 0.025)
apply_all(dish_src)
for s, name in ((1, 'Upper'), (-1, 'Lower')):
th = s * math.pi / 2
base = P(HGA_X, R_LS - 0.02, th)
tip = P(HGA_X, R_LS + BOOM, th)
boom = cylinder(f'HGA_{name}_Boom', 0.09, BOOM + 0.02, loc=base, rot=(0 if s > 0 else math.pi, 0, 0), material=M_AFT, seg=14)
box(f'HGA_{name}_Boom_Hinge', (0.22, 0.22, 0.22), loc=P(HGA_X, R_LS + 0.10, th), material=M_MECH)
box(f'HGA_{name}_Gimbal', (0.26, 0.26, 0.24), loc=tip, material=M_MECH)
n = Vector((0.55, -0.80, 0.22 * s)).normalized()
q = n.to_track_quat('Z', 'Y')
D = Matrix.Translation(tip + n * 0.22) @ q.to_matrix().to_4x4()
dish = dish_src if s > 0 else duplicate(dish_src)
dish.name = f'HGA_{name}_Dish'
dish.matrix_world = D
yoke = cylinder(f'HGA_{name}_Yoke', 0.05, 0.24, material=M_MECH, seg=10)
yoke.matrix_world = Matrix.Translation(tip) @ q.to_matrix().to_4x4()
feed = cylinder(f'HGA_{name}_Feed', 0.06, 0.12, material=M_MECH, seg=12, base=False)
feed.matrix_world = D @ Matrix.Translation((0, 0, f_len))
for k in range(3):
a = 2 * math.pi * k / 3
rim = D @ Vector((0.6 * math.cos(a), 0.6 * math.sin(a), 0.6 ** 2 / (4 * f_len)))
curve_tube(f'HGA_{name}_Feed_Strut_{k}', [tuple(rim), tuple(D @ Vector((0, 0, f_len - 0.05)))],
radius=0.012, material=M_MECH, resolution=1, bevel_res=1)
# ---------------------------------------------------------------- 8. 전시 받침대
STAND_X = 1.75
cylinder('Display_Stand_Base', 1.5, 0.14, loc=(STAND_X, 0, 0), material=M_STAND, seg=48)
cylinder('Display_Stand_Plinth', 0.45, 0.20, loc=(STAND_X, 0, 0.14), material=M_STAND, seg=32)
cylinder('Display_Stand_Post', 0.14, H - R_AFT - 0.34 + 0.05, loc=(STAND_X, 0, 0.34), material=M_STAND, seg=20)
shell_patch('Display_Stand_Cradle', R_AFT + 0.0, 0.06, math.radians(-106), math.radians(-74), STAND_X - 0.35, STAND_X + 0.35, M_STAND)

다른 모형