본문으로 건너뛰기
3D 아틀리에
에펠탑 3D 모형

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

이 모형에 대하여

에펠탑은 귀스타브 에펠의 회사가 1889년 파리 만국박람회에 맞춰 샹드마르스 공원에 세운 연철 탑이다. 약 1만 8천 개의 부재를 리벳으로 이어 2년 남짓한 공사 끝에 완공했으며, 완공 당시 세계에서 가장 높은 구조물이었다. 처음에는 박람회 뒤 철거될 예정이었으나 무선 송신 시설로 쓰이면서 남게 되었다.

탑은 사각형 평면 모서리에 놓인 네 개의 다리에서 시작한다. 다리는 각각 네 개의 모서리 기둥과 그 사이의 격자로 된 상자형 트러스이며, 바람의 힘을 고려해 바깥선이 오목한 곡선을 그리며 위로 갈수록 모여 하나의 탑신이 된다. 1층 아래 네 면에는 장식 아치가 걸려 있고, 약 58m와 116m 높이에 1층·2층 전망대가, 약 276m에 3층 전망대가 있다. 꼭대기에는 안테나가 더해져 전체 높이는 약 330m다.

이 모델은 높이 330m의 실제 크기로, 오목한 윤곽선과 네 다리의 상자형 트러스, 면마다 반복되는 X자 사재, 1층 아래의 아치, 아케이드 띠를 두른 1·2층 전망대와 파빌리온, 3층의 유리 객실과 등탑, 안테나를 표현했다.

만든 방법

탑의 바깥 반폭과 다리 안쪽 모서리 위치를 높이에 대한 단조 3차 보간(PCHIP) 곡선 두 개로 정의하고, 모든 부재의 위치를 이 곡선에서 계산했다. 다리 하나를 bmesh 로 직접 짠 뒤(모서리 주재는 수평 정사각 단면을 이어 붙인 연속 관, 사재와 수평재는 두 줄 플랜지와 지그재그 웨브로 된 격자 거더) 90°씩 회전한 연결 복제로 네 다리를 놓았다. 아치와 파빌리온도 같은 방식으로 한 면만 만들어 회전 복제했다. 실제 탑의 2차·3차 잔 격자, 리벳, 계단, 경사 엘리베이터, 조명 설비는 생략했고 3층 객실과 꼭대기 구조는 형태를 단순화했다. 재질은 GLB 에서도 같은 색이 나오도록 Principled BSDF 상수 값만 썼다.

build.py

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

# 에펠탑 (프랑스 파리, 1889) — 실제 크기(높이 330m) 코드 모델.
# 탑의 바깥 윤곽 W(z)와 다리 안쪽 모서리 I(z)를 단조 3차 보간(PCHIP) 곡선으로 정의하고,
# 네 다리를 '네 개의 모서리 주재(chord) + 면마다 X자 사재와 수평재'로 된 상자형 트러스로 짰다.
# 사재·수평재는 다시 두 줄 플랜지와 지그재그 웨브로 된 격자 거더라서 멀리서도 연철 격자가 읽힌다.
# 다리 하나를 bmesh 로 만든 뒤 90°씩 회전한 연결 복제(linked duplicate)로 나머지 셋을 놓았다.
# 1층 아래 네 면의 장식 아치, 아케이드 띠를 두른 1·2층 전망대, 3층 유리 객실과 꼭대기 등탑·안테나를 더했다.
# 리벳, 계단, 경사 엘리베이터, 2차 이하의 잔 격자는 생략했다.
from atelier import *
SHOT = {"azimuth": -70, "elevation": 6, "lens": 60, "light": "day", "background": "#232c3b"}
# ---------------------------------------------------------------- 재질
IRON = mat('Eiffel_Bronze_Paint', '#7a6350', 0.55, 0.3)
IRON_DARK = mat('Iron_Secondary', '#584536', 0.6, 0.3)
STONE = mat('Masonry_Pier', '#9a8f7e', 0.85, 0.0)
DECK = mat('Deck_Planking', '#5a4e44', 0.8, 0.05)
GLAZE = mat('Glazing', '#405463', 0.1, 0.6)
ANT_W = mat('Antenna_White', '#d2cdc3', 0.45, 0.1)
ANT_R = mat('Antenna_Red', '#a43a2e', 0.45, 0.1)
LAMP = mat('Beacon_Brass', '#c69a4c', 0.3, 0.85, emit='#ffcf7a', emit_strength=1.5)
# ---------------------------------------------------------------- 윤곽 곡선
def pchip(xs, ys):
n = len(xs)
h = [xs[i + 1] - xs[i] for i in range(n - 1)]
d = [(ys[i + 1] - ys[i]) / h[i] for i in range(n - 1)]
m = [0.0] * n
m[0], m[-1] = d[0], d[-1]
for i in range(1, n - 1):
if d[i - 1] * d[i] <= 0:
m[i] = 0.0
else:
w1, w2 = 2 * h[i] + h[i - 1], h[i] + 2 * h[i - 1]
m[i] = (w1 + w2) / (w1 / d[i - 1] + w2 / d[i])
def f(x):
x = min(max(x, xs[0]), xs[-1])
i = 0
while i < n - 2 and x > xs[i + 1]:
i += 1
t = (x - xs[i]) / h[i]
t2, t3 = t * t, t * t * t
return ((2 * t3 - 3 * t2 + 1) * ys[i] + (t3 - 2 * t2 + t) * h[i] * m[i]
+ (-2 * t3 + 3 * t2) * ys[i + 1] + (t3 - t2) * h[i] * m[i + 1])
return f
나머지 411줄 펼치기
# 바깥 반폭(중심→바깥 모서리)과 다리 안쪽 모서리까지의 거리
W = pchip([0, 30, 57.6, 90, 115.7, 150, 195, 240, 276, 300],
[62.5, 47.5, 36.0, 26.4, 20.8, 15.4, 11.2, 8.4, 6.6, 5.2])
I = pchip([0, 57.6, 115.7, 150, 185, 200], [36.5, 16.8, 7.5, 4.2, 2.8, 2.8])
F1, F2, F3 = 57.6, 115.7, 276.0 # 1·2·3층 바닥 높이
PIER = 4.0 # 석조 기초 높이
Z_LEG = [PIER, 18, 31, 44, F1, 68, 78, 88, 97, 106.5, F2, 126, 136, 146, 156, 166, 176, 186, 195]
Z_SHAFT = [195, 213, 230, 246, 261, F3]
def chord_size(z):
return 2.3 - 1.3 * min(z, 195) / 195 - 0.3 * max(z - 195, 0) / 81
# ---------------------------------------------------------------- bmesh 도우미
class MB:
def __init__(self):
self.bm = bmesh.new()
def quad(self, vs):
try:
self.bm.faces.new(vs)
except ValueError:
pass
def bar(self, p0, p1, t, n, t2=None):
"""p0→p1 사각 단면 부재. n 은 단면 방향을 잡는 법선 힌트."""
p0, p1 = Vector(p0), Vector(p1)
d = p1 - p0
if d.length < 1e-4:
return
d.normalize()
u = d.cross(Vector(n))
if u.length < 1e-4:
u = d.cross(Vector((1, 0, 0)))
u.normalize()
v = u.cross(d).normalized()
a, b = t / 2, (t2 if t2 else t) / 2
offs = [u * a + v * b, -u * a + v * b, -u * a - v * b, u * a - v * b]
r0 = [self.bm.verts.new(p0 + o) for o in offs]
r1 = [self.bm.verts.new(p1 + o) for o in offs]
for i in range(4):
j = (i + 1) % 4
self.quad((r0[i], r0[j], r1[j], r1[i]))
self.quad(list(reversed(r0)))
self.quad(r1)
def girder(self, p0, p1, n, width, t, off=0.0):
"""두 줄 플랜지 + 지그재그 웨브의 평면 격자 거더."""
p0, p1, n = Vector(p0), Vector(p1), Vector(n).normalized()
d = p1 - p0
L = d.length
w = n.cross(d.normalized()).normalized() * (width / 2)
s = n * off
a0, a1, b0, b1 = p0 + w + s, p1 + w + s, p0 - w + s, p1 - w + s
self.bar(a0, a1, t, n)
self.bar(b0, b1, t, n)
k = max(2, round(L / (width * 1.15)))
pts = [(a0.lerp(a1, i / k) if i % 2 == 0 else b0.lerp(b1, i / k)) for i in range(k + 1)]
for i in range(k):
self.bar(pts[i], pts[i + 1], t * 0.6, n)
def sweep(self, pts, sizes):
"""세로 주재: 각 점에서 수평 정사각 단면을 이어 붙인 연속 관."""
rings = []
for (x, y, z), s in zip(pts, sizes):
h = s / 2
rings.append([self.bm.verts.new((x + dx * h, y + dy * h, z))
for dx, dy in ((1, 1), (-1, 1), (-1, -1), (1, -1))])
for r0, r1 in zip(rings, rings[1:]):
for i in range(4):
j = (i + 1) % 4
self.quad((r0[i], r0[j], r1[j], r1[i]))
self.quad(list(reversed(rings[0])))
self.quad(rings[-1])
def sq_ring(self, outer, inner, z0, z1):
"""가운데가 뚫린 정사각 고리 판(바닥판, 띠)."""
def ring(hh, z):
return [self.bm.verts.new((sx * hh, sy * hh, z)) for sx, sy in ((1, 1), (-1, 1), (-1, -1), (1, -1))]
ob, ot, ib, it = ring(outer, z0), ring(outer, z1), ring(inner, z0), ring(inner, z1)
for i in range(4):
j = (i + 1) % 4
self.quad((ob[i], ob[j], ot[j], ot[i]))
self.quad((it[i], it[j], ib[j], ib[i]))
self.quad((ot[i], ot[j], it[j], it[i]))
self.quad((ib[i], ib[j], ob[j], ob[i]))
def block(self, c, size):
(x, y, z), (sx, sy, sz) = c, size
self.bar((x, y, z - sz / 2), (x, y, z + sz / 2), sx, (0, 1, 0), sy)
def finish(self, name, material, smooth_=False):
bmesh.ops.remove_doubles(self.bm, verts=self.bm.verts, dist=1e-4)
bmesh.ops.recalc_face_normals(self.bm, faces=self.bm.faces)
me = bpy.data.meshes.new(name)
self.bm.to_mesh(me)
self.bm.free()
ob = bpy.data.objects.new(name, me)
link(ob)
assign(ob, material)
if smooth_:
smooth(ob)
return ob
def frange(a, b, step):
n = max(1, round((b - a) / step))
return [a + (b - a) * i / n for i in range(n + 1)]
def face_normal(a0, b0, a1, outward):
n = (Vector(b0) - Vector(a0)).cross(Vector(a1) - Vector(a0)).normalized()
return n if n.dot(Vector(outward)) >= 0 else -n
def lattice_face(mb, A, B, zs, outward, hw, dw, t, skip_first_h=False, split=1):
"""두 주재 A(z), B(z) 사이의 면: 마디마다 수평 거더, split 칸으로 나눠 칸마다 X 사재."""
cols = [(lambda z, f=i / split: Vector(A(z)).lerp(Vector(B(z)), f)) for i in range(split + 1)]
for k, z in enumerate(zs):
a, b = A(z), B(z)
z1 = zs[min(k + 1, len(zs) - 1)]
n = face_normal(a, b, A(z1) if k + 1 < len(zs) else A(zs[k - 1]), outward)
if not (k == 0 and skip_first_h):
mb.girder(a, b, n, hw(z), t, off=-0.05)
if k + 1 < len(zs):
for j in range(split):
p, q = cols[j], cols[j + 1]
mb.girder(p(z), q(z1), n, dw(z), t, off=0.0)
mb.girder(q(z), p(z1), n, dw(z), t, off=0.09)
for j in range(1, split):
mb.girder(cols[j](z), cols[j](z1), n, dw(z) * 0.8, t, off=0.04)
# ---------------------------------------------------------------- 다리 (하나 만들고 회전 복제)
def build_leg():
mb = MB()
P = {
'WW': lambda z: (W(z), W(z), z),
'WI': lambda z: (W(z), I(z), z),
'IW': lambda z: (I(z), W(z), z),
'II': lambda z: (I(z), I(z), z),
}
# 모서리 주재 — 바깥 모서리는 3층까지 이어져 상부 탑신의 모서리가 된다
for key, top in (('WW', F3), ('WI', 195), ('IW', 195), ('II', 195)):
zs = frange(PIER, top, 2.5)
mb.sweep([P[key](z) for z in zs], [chord_size(z) for z in zs])
hw = lambda z: 0.7 + 0.03 * (W(z) - I(z))
dw = lambda z: 0.5 + 0.016 * (W(z) - I(z))
lattice_face(mb, P['WI'], P['WW'], Z_LEG, (1, 0, 0), hw, dw, 0.3, split=2)
lattice_face(mb, P['IW'], P['WW'], Z_LEG, (0, 1, 0), hw, dw, 0.3, split=2)
lattice_face(mb, P['II'], P['IW'], Z_LEG, (-1, 0, 0), hw, dw, 0.3, split=2)
lattice_face(mb, P['II'], P['WI'], Z_LEG, (0, -1, 0), hw, dw, 0.3, split=2)
# 다리 내부의 수평 가새(마디마다 대각선)
for z in Z_LEG[1:]:
mb.bar(P['II'](z), P['WW'](z), 0.3, (0, 0, 1))
return mb.finish('Leg_NE', IRON)
def build_piers():
mb = MB()
for key in ((1, 1), (1, 0), (0, 1), (0, 0)):
x = W(PIER) if key[0] else I(PIER)
y = W(PIER) if key[1] else I(PIER)
mb.block((x, y, 1.2), (8.0, 8.0, 2.4))
mb.block((x, y, 2.4 + (PIER - 2.4) / 2 + 0.3), (5.0, 5.0, PIER - 2.4 + 0.6))
return mb.finish('Masonry_Piers_NE', STONE)
# ---------------------------------------------------------------- 상부 탑신 (195m → 3층)
def build_shaft():
mb = MB()
corners = [(1, 1), (-1, 1), (-1, -1), (1, -1)]
for i in range(4):
sa, sb = corners[i], corners[(i + 1) % 4]
A = lambda z, s=sa: (s[0] * W(z), s[1] * W(z), z)
B = lambda z, s=sb: (s[0] * W(z), s[1] * W(z), z)
out = ((sa[0] + sb[0]) / 2, (sa[1] + sb[1]) / 2, 0)
lattice_face(mb, A, B, Z_SHAFT, out, lambda z: 1.0, lambda z: 0.85, 0.26, skip_first_h=True, split=2)
for z in Z_SHAFT[1:-1]:
mb.bar((W(z), W(z), z), (-W(z), -W(z), z), 0.28, (0, 0, 1))
mb.bar((-W(z), W(z), z), (W(z), -W(z), z), 0.28, (0, 0, 1))
return mb.finish('Upper_Shaft', IRON)
def build_lift_column():
"""2층에서 3층까지 탑 중심을 오르는 엘리베이터 기둥."""
mb = MB()
h = 1.6
for sx, sy in ((1, 1), (-1, 1), (-1, -1), (1, -1)):
mb.sweep([(sx * h, sy * h, F2 - 0.6), (sx * h, sy * h, F3)], [0.45, 0.45])
zs = frange(F2, F3, 8.0)
for z in zs:
mb.sq_ring(h + 0.18, h - 0.18, z - 0.15, z + 0.15)
for z0, z1 in zip(zs, zs[1:]):
for (ax, ay), (bx, by), n in (((1, 1), (1, -1), (1, 0, 0)), ((-1, 1), (-1, -1), (-1, 0, 0)),
((1, 1), (-1, 1), (0, 1, 0)), ((1, -1), (-1, -1), (0, -1, 0))):
mb.bar((ax * h, ay * h, z0), (bx * h, by * h, z1), 0.18, n)
return mb.finish('Lift_Column', IRON_DARK)
# ---------------------------------------------------------------- 1층 아래 장식 아치 (앞면 -Y, 회전 복제)
def build_arch():
mb, web = MB(), MB()
z0, depth = 12.0, 3.6
a_in = I(z0) + 0.5
b_in = 50.6 - depth - z0
ints, exts = [], []
N = 72
for i in range(N + 1):
ph = math.pi * i / N
x, z = a_in * math.cos(ph), z0 + b_in * math.sin(ph)
if abs(x) > I(z) + 0.6:
continue
xe, ze = (a_in + depth) * math.cos(ph), z0 + (b_in + depth) * math.sin(ph)
ints.append((x, -(W(z) - 1.0), z))
exts.append((xe, -(W(ze) - 1.0), ze))
n = (0, -1, 0.0)
for crv in (ints, exts):
for p, q in zip(crv, crv[1:]):
mb.bar(p, q, 0.8, n, 1.1)
for i in range(len(ints) - 1):
a, b, c, d = (Vector(v) for v in (ints[i], ints[i + 1], exts[i + 1], exts[i]))
sh = Vector((0, 0.35, 0))
web.quad([web.bm.verts.new(v + sh) for v in (a, b, c, d)])
web.quad([web.bm.verts.new(v + sh * 1.6) for v in (d, c, b, a)])
mb.bar(ints[i], exts[i + 1] if i % 2 == 0 else exts[i], 0.3, n)
mb.bar(exts[i] if i % 2 == 0 else exts[i + 1], ints[i + 1], 0.3, n)
for i in range(0, len(ints), 3):
mb.bar(ints[i], exts[i], 0.35, n)
return mb.finish('Arch_South', IRON), web.finish('Arch_South_Web', IRON_DARK)
# ---------------------------------------------------------------- 전망대
def arcade_band(mb, D, z0, z1, post_step, t=0.5):
"""전망대 둘레의 아케이드 띠: 위아래 띠장 + 기둥 + 작은 반원 아치."""
mb.sq_ring(D + t / 2, D - t / 2, z0, z0 + t * 1.4)
mb.sq_ring(D + t / 2 + 0.05, D - t / 2 - 0.05, z1 - t * 1.6, z1)
hgt = z1 - z0
for sx, sy, ax in ((0, -1, 'x'), (0, 1, 'x'), (-1, 0, 'y'), (1, 0, 'y')):
xs = frange(-D, D, post_step)
n = (sx, sy, 0)
def P(u, z):
return (u, sy * D, z) if ax == 'x' else (sx * D, u, z)
for u in xs[1:-1]:
mb.bar(P(u, z0 + t), P(u, z1 - t), t * 0.8, n)
spring = z0 + hgt * 0.45
for u0, u1 in zip(xs, xs[1:]):
r = (u1 - u0) / 2
c = (u0 + u1) / 2
arc = [P(c + r * math.cos(math.pi * k / 8), spring + r * 0.9 * math.sin(math.pi * k / 8)) for k in range(9)]
for p, q in zip(arc, arc[1:]):
mb.bar(p, q, t * 0.45, n)
mb.bar(P(c, spring + r * 0.9), P(c, z1 - t), t * 0.35, n)
def railing(mb, D, z, step=2.0, hgt=1.2):
mb.sq_ring(D + 0.08, D - 0.08, z + hgt - 0.12, z + hgt)
mb.sq_ring(D + 0.04, D - 0.04, z + hgt * 0.5 - 0.05, z + hgt * 0.5 + 0.05)
for sx, sy, ax in ((0, -1, 'x'), (0, 1, 'x'), (-1, 0, 'y'), (1, 0, 'y')):
for u in frange(-D, D, step)[:-1]:
p = (u, sy * D) if ax == 'x' else (sx * D, u)
mb.bar((p[0], p[1], z), (p[0], p[1], z + hgt - 0.1), 0.1, (1, 0, 0))
def build_floor(name, D, deck_inner, zf, band_h, post_step, rail_step):
iron, deck, back = MB(), MB(), MB()
t = 0.6 if band_h > 5 else 0.45
arcade_band(iron, D, zf - band_h, zf + 0.6, post_step, t=t)
back.sq_ring(D - t * 0.55, D - t * 0.55 - 0.25, zf - band_h + 0.2, zf + 0.4)
railing(iron, D - 0.2, zf + 0.6, rail_step)
deck.sq_ring(D - 0.25, deck_inner, zf - 0.6, zf + 0.0)
back.finish(name + '_Frieze_Panel', IRON_DARK)
return iron.finish(name + '_Gallery', IRON), deck.finish(name + '_Deck', DECK)
def build_pavilion(name, half_x, y0, y1, z0, z1):
"""전망대 위 유리 파빌리온(1층 레스토랑 등) — 앞면(-Y) 한 채."""
glass_mb, frame = MB(), MB()
cx, cy, h = 0.0, (y0 + y1) / 2, z1 - z0
glass_mb.block((cx, cy, z0 + (h - 0.8) / 2), (2 * half_x - 0.4, abs(y1 - y0) - 0.4, h - 0.8))
frame.block((cx, cy, z1 - 0.4), (2 * half_x + 0.6, abs(y1 - y0) + 0.6, 0.8))
for u in frange(-half_x, half_x, 2.2):
for yy in (y0, y1):
frame.bar((u, yy, z0), (u, yy, z1 - 0.8), 0.22, (0, -1, 0))
for v in frange(min(y0, y1), max(y0, y1), 2.2)[1:-1]:
for xx in (-half_x, half_x):
frame.bar((xx, v, z0), (xx, v, z1 - 0.8), 0.22, (1, 0, 0))
for yy in (y0, y1):
frame.bar((-half_x, yy, z0 + h * 0.55), (half_x, yy, z0 + h * 0.55), 0.18, (0, -1, 0))
return glass_mb.finish(name + '_Glass', GLAZE), frame.finish(name + '_Frame', IRON)
# ---------------------------------------------------------------- 3층과 꼭대기
def build_summit():
iron, glass_mb, deck, white, red, lamp = MB(), MB(), MB(), MB(), MB(), MB()
zf = F3
deck.sq_ring(9.8, 2.2, zf - 1.0, zf)
iron.sq_ring(9.95, 9.55, zf - 1.6, zf + 0.1)
# 아래층 유리 객실
glass_mb.sq_ring(7.6, 2.4, zf, zf + 3.4)
for sx, sy, ax in ((0, -1, 'x'), (0, 1, 'x'), (-1, 0, 'y'), (1, 0, 'y')):
for u in frange(-7.75, 7.75, 1.94):
p = (u, sy * 7.75) if ax == 'x' else (sx * 7.75, u)
iron.bar((p[0], p[1], zf), (p[0], p[1], zf + 3.4), 0.16, (1, 0, 0))
iron.sq_ring(8.7, 2.4, zf + 3.4, zf + 4.0)
railing(iron, 8.5, zf + 4.0, 1.6, 1.3)
# 위층 사무실(에펠의 방)
iron.sq_ring(4.6, 2.4, zf + 4.0, zf + 7.2)
for u in frange(-4.0, 4.0, 2.0):
for s in (-1, 1):
glass_mb.block((u, s * 4.65, zf + 5.6), (1.1, 0.12, 1.6))
glass_mb.block((s * 4.65, u, zf + 5.6), (0.12, 1.1, 1.6))
deck.sq_ring(5.1, 0.0001, zf + 7.2, zf + 7.7)
# 등탑을 받치는 네 모서리 리브와 면 아치
zb, zt = zf + 4.0, 297.0
for sx, sy in ((1, 1), (-1, 1), (-1, -1), (1, -1)):
p0 = Vector((sx * 8.0, sy * 8.0, zb))
c = Vector((sx * 6.8, sy * 6.8, 291.0))
p1 = Vector((sx * 1.9, sy * 1.9, zt))
pts = [(1 - t) ** 2 * p0 + 2 * (1 - t) * t * c + t * t * p1 for t in [i / 12 for i in range(13)]]
for p, q in zip(pts, pts[1:]):
iron.bar(p, q, 0.55, (sx, -sy, 0))
for sx, sy, ax in ((0, -1, 'x'), (0, 1, 'x'), (-1, 0, 'y'), (1, 0, 'y')):
arc = []
for k in range(17):
ph = math.pi * k / 16
u = 7.6 * math.cos(ph)
z = zf + 7.7 + 7.0 * math.sin(ph)
off = 7.6 - 1.0 * math.sin(ph)
arc.append((u, sy * off, z) if ax == 'x' else (sx * off, u, z))
for p, q in zip(arc, arc[1:]):
iron.bar(p, q, 0.4, (sx, sy, 0))
for z, hh in ((289.0, 5.5), (293.5, 3.9)):
iron.sq_ring(hh + 0.15, hh - 0.15, z - 0.15, z + 0.15)
# 등탑과 돔
iron.sq_ring(2.6, 0.0001, zt - 0.3, zt + 0.3)
bm = lamp.bm
bmesh.ops.create_cone(bm, cap_ends=True, segments=24, radius1=1.7, radius2=1.7, depth=3.2,
matrix=Matrix.Translation((0, 0, zt + 0.3 + 1.6)))
for k in range(8):
a = 2 * math.pi * k / 8
iron.bar((1.78 * math.cos(a), 1.78 * math.sin(a), zt + 0.3), (1.78 * math.cos(a), 1.78 * math.sin(a), zt + 3.5), 0.18, (math.cos(a), math.sin(a), 0))
prof = [(2.3, zt + 3.5), (2.3, zt + 3.9), (2.0, zt + 4.6), (1.3, zt + 5.3), (0.6, zt + 5.7), (0.0001, zt + 5.8)]
rings = []
for i in range(24):
a = 2 * math.pi * i / 24
rings.append([iron.bm.verts.new((r * math.cos(a), r * math.sin(a), z)) for r, z in prof])
for i in range(24):
r0, r1 = rings[i], rings[(i + 1) % 24]
for j in range(len(prof) - 1):
iron.quad((r0[j], r1[j], r1[j + 1], r0[j + 1]))
# 안테나 (330m 까지)
z = zt + 5.6
bands = [(1.1, 5.0, white), (1.0, 3.0, red), (0.9, 3.0, white), (0.75, 3.0, red), (0.65, 3.0, white),
(0.5, 2.6, red), (0.4, 2.6, white), (0.3, 2.4, red), (0.18, 330.0 - z - 24.6, white)]
for r, hgt, part in bands:
bmesh.ops.create_cone(part.bm, cap_ends=True, segments=16, radius1=r, radius2=r * 0.95, depth=hgt,
matrix=Matrix.Translation((0, 0, z + hgt / 2)))
z += hgt
for zz, rr in ((zt + 10.6, 2.0), (zt + 16.6, 1.5)):
bmesh.ops.create_cone(white.bm, cap_ends=True, segments=20, radius1=rr, radius2=rr, depth=0.25,
matrix=Matrix.Translation((0, 0, zz)))
return [iron.finish('Summit_Ironwork', IRON), glass_mb.finish('Summit_Cabin_Glass', GLAZE),
deck.finish('Summit_Deck', DECK), white.finish('Antenna_Mast', ANT_W, True),
red.finish('Antenna_Red_Bands', ANT_R, True), lamp.finish('Beacon_Lantern', LAMP, True)]
# ---------------------------------------------------------------- 조립
def rotated(ob, k, name):
return ob if k == 0 else duplicate(ob, rot=(0, 0, k * math.pi / 2), name=name)
leg = build_leg()
piers = build_piers()
for k, nm in enumerate(['NE', 'NW', 'SW', 'SE']):
rotated(leg, k, 'Leg_' + nm)
rotated(piers, k, 'Masonry_Piers_' + nm)
arch, arch_web = build_arch()
for k, nm in enumerate(['South', 'East', 'North', 'West']):
rotated(arch, k, 'Arch_' + nm)
rotated(arch_web, k, 'Arch_%s_Web' % nm)
build_shaft()
build_lift_column()
D1 = W(F1) + 1.0
build_floor('Floor1', D1, 14.0, F1, 6.6, 3.0, 2.0)
pg, pf = build_pavilion('Floor1_Pavilion_S', 13.0, -(D1 - 1.6), -(D1 - 9.0), F1, F1 + 6.2)
for k, nm in enumerate(['S', 'E', 'N', 'W']):
rotated(pg, k, 'Floor1_Pavilion_%s_Glass' % nm)
rotated(pf, k, 'Floor1_Pavilion_%s_Frame' % nm)
D2 = W(F2) + 0.8
build_floor('Floor2', D2, 3.8, F2, 3.8, 2.4, 2.0)
pg2, pf2 = build_pavilion('Floor2_Pavilion_S', 6.0, -(D2 - 1.4), -(D2 - 6.4), F2, F2 + 5.6)
for k, nm in enumerate(['S', 'E', 'N', 'W']):
rotated(pg2, k, 'Floor2_Pavilion_%s_Glass' % nm)
rotated(pf2, k, 'Floor2_Pavilion_%s_Frame' % nm)
up = MB()
up.sq_ring(W(F2 + 6.2) + 0.3, 9.0, F2 + 5.6, F2 + 6.2)
up.finish('Floor2_Upper_Deck', DECK)
upr = MB()
railing(upr, W(F2 + 6.2) + 0.1, F2 + 6.2, 2.0)
upr.finish('Floor2_Upper_Railing', IRON)
for ob in build_summit():
ob.scale = (0.88, 0.88, 1.0) # 3층 객실 평면을 상부 탑신 폭에 맞춘다

다른 모형