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

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

이 모형에 대하여

금문교는 미국 샌프란시스코와 북쪽 마린 카운티 사이의 골든게이트 해협을 건너는 현수교다. 1933년에 착공해 1937년에 개통했으며, 개통 당시 주경간 1,280m 로 세계에서 가장 긴 현수교였다. 수면 위 약 227m 높이의 주탑 두 개가 두 가닥의 주케이블을 받치고, 케이블에서 내려온 행어 로프가 노면을 매단다.

건축가 어빙 모로가 다듬은 아르데코 양식의 주탑은 위로 올라갈수록 단을 지어 가늘어지고, 두 기둥 사이를 잇는 가로보와 기둥 면의 세로 홈이 특징이다. 다리 전체를 덮은 '인터내셔널 오렌지' 색은 안개 속에서도 잘 보이고 주변 지형과 어울리도록 정해졌다.

이 모델은 측경간 끝의 콘크리트 앵커리지까지를 실제 치수로 표현했다. 남쪽 주탑의 타원형 방현벽, 노면 아래의 보강 트러스, 15m 남짓한 간격으로 늘어선 행어 로프와 가로등, 주탑 꼭대기의 항공 장애등을 담았다.

만든 방법

모든 부품은 bpy/bmesh 로 코드에서 생성했다. 주탑은 높이별로 단면이 줄어드는 상자 기둥에 얇은 필라스터와 가로보 리브를 덧붙여 만들고, 한 개를 만든 뒤 메시를 공유하는 복제로 두 번째 주탑을 세웠다. 주케이블은 주경간 포물선과 측경간 곡선 위의 점을 따라 원형 단면을 쓸어 만들었고, 행어 로프와 케이블 밴드는 15.24m 간격으로 배열했다. 보강 트러스는 7.62m 패널마다 수직재·사재·가로보·하부 X 브레이싱을 계산해 배치했고, 노면은 가운데가 약간 솟은 캠버 곡선을 따른다. 접근 고가교, 포트 포인트 위의 아치, 지형과 바다는 생략했고 주탑 내부의 셀 구조와 리벳은 표현하지 않았다.

build.py

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

# 금문교 (Golden Gate Bridge, 샌프란시스코, 1937) — 실제 치수(미터)로 만든 현수교 모델.
# 주경간 1,280m, 측경간 343m, 수면 위 주탑 높이 227m 를 기준으로 했다(바닥 z=0 이 수면).
# 주탑은 두 기둥과 가로보(포털 스트럿)를 bmesh 상자로 쌓아 단계적으로 가늘어지는 모양과
# 세로 홈(필라스터)을 표현했고, 한 개를 만든 뒤 메시를 공유하는 복제로 두 번째 주탑을 세웠다.
# 주케이블은 포물선 위의 점을 따라 원형 단면을 쓸어 만들었고, 행어 로프는 15.24m 간격으로 배열했다.
# 보강 트러스는 7.62m 패널마다 수직재·사재·하부 수평 브레이싱을 수학적으로 배치했다.
# 색은 인터내셔널 오렌지 도장, 콘크리트 교각과 앵커리지, 아스팔트 노면으로 단순화했다.
from atelier import *
SHOT = {"azimuth": -26, "elevation": 7, "lens": 55, "light": "golden", "background": "#1e2a3a"}
# ---------------------------------------------------------------- 재질
ORANGE = mat('International Orange', '#b8402a', rough=0.55, metal=0.15)
CABLE = mat('Cable Orange', '#a83a26', rough=0.5, metal=0.2)
CONCRETE = mat('Pier Concrete', '#a59f93', rough=0.85)
CONCRETE_DK = mat('Weathered Concrete', '#7d786f', rough=0.9)
ASPHALT = mat('Asphalt', '#3a3a3d', rough=0.9)
WALK = mat('Sidewalk Concrete', '#8f8a82', rough=0.85)
LAMP = mat('Sodium Lamp', '#f3c98a', rough=0.3, emit='#ffb860', emit_strength=3.0)
BEACON = mat('Aviation Beacon', '#c42020', rough=0.3, emit='#ff2a1a', emit_strength=6.0)
# ---------------------------------------------------------------- 치수
TOWER_X = 640.0 # 주경간 1280m 의 절반
ANCHOR_X = 990.0 # 측경간 끝(앵커리지 파일론)
PANEL = 7.62 # 트러스 패널 25ft
NPANEL = 136
DECK_END = NPANEL * PANEL # 1036.32
CABLE_Y = 13.7 # 케이블 간격 90ft 의 절반
TOWER_TOP = 227.0
SADDLE_Z = 225.0
def road_z(x):
"""노면 높이: 가운데가 살짝 솟은 캠버, 앵커리지 위는 평평."""
xa = min(abs(x), ANCHOR_X)
return 72.0 + 3.0 * (1.0 - (xa / 1100.0) ** 2)
CABLE_LOW = road_z(0) + 4.0
def cable_z(x):
xa = abs(x)
if xa <= TOWER_X:
return CABLE_LOW + (SADDLE_Z - CABLE_LOW) * (xa / TOWER_X) ** 2
t = (xa - TOWER_X) / (ANCHOR_X - TOWER_X)
z_end = road_z(ANCHOR_X) + 12.0
return SADDLE_Z + (z_end - SADDLE_Z) * t - 10.0 * 4 * t * (1 - t)
나머지 417줄 펼치기
# ---------------------------------------------------------------- bmesh 도우미
def bm_box(bm, center, size):
bmesh.ops.create_cube(bm, size=1.0, matrix=Matrix.LocRotScale(Vector(center), None, Vector(size)))
def bm_beam(bm, p0, p1, w, h):
"""p0→p1 을 잇는 직사각 단면 부재(w: 수평 폭, h: 높이 방향)."""
p0, p1 = Vector(p0), Vector(p1)
d = (p1 - p0).normalized()
ref = Vector((0, 0, 1)) if abs(d.z) < 0.95 else Vector((1, 0, 0))
s = d.cross(ref).normalized()
u = s.cross(d).normalized()
vs = []
for p in (p0, p1):
for a, b in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
vs.append(bm.verts.new(p + s * (a * w / 2) + u * (b * h / 2)))
f = [(0, 1, 2, 3), (7, 6, 5, 4), (0, 4, 5, 1), (1, 5, 6, 2), (2, 6, 7, 3), (3, 7, 4, 0)]
for q in f:
bm.faces.new([vs[i] for i in q])
def bm_tube(bm, pts, r, seg=12, caps=True):
pts = [Vector(p) for p in pts]
rings = []
for i, p in enumerate(pts):
t = (pts[min(i + 1, len(pts) - 1)] - pts[max(i - 1, 0)]).normalized()
ref = Vector((0, 1, 0)) if abs(t.y) < 0.9 else Vector((1, 0, 0))
n1 = t.cross(ref).normalized()
n2 = t.cross(n1).normalized()
rings.append([bm.verts.new(p + (n1 * math.cos(2 * math.pi * k / seg) + n2 * math.sin(2 * math.pi * k / seg)) * r)
for k in range(seg)])
for a, b in zip(rings, rings[1:]):
for k in range(seg):
bm.faces.new((a[k], a[(k + 1) % seg], b[(k + 1) % seg], b[k]))
if caps:
bm.faces.new(rings[0])
bm.faces.new(list(reversed(rings[-1])))
def bm_sweep(bm, xs, sec, zf=road_z):
"""YZ 단면(sec=[(y, dz)...])을 노면 곡선을 따라 X 로 쓸기."""
rings = [[bm.verts.new((x, y, zf(x) + dz)) for y, dz in sec] for x in xs]
n = len(sec)
for a, b in zip(rings, rings[1:]):
for j in range(n):
bm.faces.new((a[j], b[j], b[(j + 1) % n], a[(j + 1) % n]))
bm.faces.new(list(reversed(rings[0])))
bm.faces.new(rings[-1])
def rect(y0, y1, z0, z1):
return [(y0, z0), (y1, z0), (y1, z1), (y0, z1)]
def finish(name, bm, material, loc=(0, 0, 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)
ob.location = Vector(loc)
return assign(ob, material)
# ---------------------------------------------------------------- 주탑
PIER_TOP = 13.0
LEG_BASE = 14.5
# (z0, z1, sx[교축 방향], sy[교폭 방향]) — 스트럿 높이마다 단을 줄인다
LEG_SEGS = [(LEG_BASE, 65.7, 16.0, 10.0), (65.7, 124.0, 14.0, 8.6), (124.0, 159.0, 12.6, 7.9),
(159.0, 190.0, 11.2, 7.2), (190.0, 222.0, 10.0, 6.5)]
STRUTS = [(116.0, 124.0), (152.0, 159.0), (184.0, 190.0), (212.0, 222.0)]
def leg_size(z):
for z0, z1, sx, sy in LEG_SEGS:
if z0 <= z <= z1:
return sx, sy
return LEG_SEGS[-1][2], LEG_SEGS[-1][3]
def build_tower(name):
bm = bmesh.new()
for sgn in (-1, 1):
yc = sgn * CABLE_Y
for z0, z1, sx, sy in LEG_SEGS:
h = z1 - z0
bm_box(bm, (0, yc, z0 - 0.05 + (h + 0.05) / 2), (sx, sy, h + 0.05))
# 세로 필라스터(셀 구조가 드러나는 홈 무늬)
pz0, pz1 = z0 + 0.3, z1 - 0.6
ph = pz1 - pz0
for fx in (-1, 1):
for oy in (-sy / 2 + 0.8, 0.0, sy / 2 - 0.8):
bm_box(bm, (fx * (sx / 2 + 0.12), yc + oy, pz0 + ph / 2), (0.5, 1.3, ph))
for fy in (-1, 1):
for ox in (-sx / 2 + 0.9, -sx / 6, sx / 6, sx / 2 - 0.9):
bm_box(bm, (ox, yc + fy * (sy / 2 + 0.12), pz0 + ph / 2), (1.3, 0.5, ph))
# 꼭대기 새들 하우징: 상자 + 반원통 지붕
bm_box(bm, (0, yc, 223.0), (9.0, 6.0, 2.1))
bm_tube(bm, [(-4.5, yc, 224.0), (4.5, yc, 224.0)], 2.9, seg=20)
# 포털 스트럿(노면 위 4단)
for zb, zt in STRUTS:
sx, sy = leg_size(zb + 0.1)
yi = CABLE_Y - sy / 2 + 0.3
depth = sx * 0.62
h = zt - zb
bm_box(bm, (0, 0, zb + h / 2), (depth, 2 * yi, h))
# 위·아래 띠 몰딩
for zz in (zb + 0.45, zt - 0.45):
bm_box(bm, (0, 0, zz), (depth + 0.7, 2 * yi, 0.9))
# 앞뒤 면의 세로 리브(아르데코 장식)
nrib = 9
for i in range(nrib):
y = -yi + 2 * yi * (i + 0.5) / nrib
for fx in (-1, 1):
bm_box(bm, (fx * (depth / 2 + 0.15), y, zb + h / 2), (0.5, 0.7, h - 1.8))
# 노면 아래 스트럿과 X 브레이싱
sx, sy = leg_size(60)
yi = CABLE_Y - sy / 2 + 0.5
bm_box(bm, (0, 0, 61.7), (sx * 0.6, 2 * yi, 8.0))
bm_box(bm, (0, 0, 61.7), (sx * 0.6 + 0.8, 2 * yi, 1.0))
for sgn in (-1, 1):
bm_beam(bm, (0, sgn * (yi + 1.2), LEG_BASE + 4.0), (0, -sgn * (yi + 1.2), 58.5), sx * 0.45, 2.6)
# 기둥 밑 받침
for sgn in (-1, 1):
bm_box(bm, (0, sgn * CABLE_Y, PIER_TOP + 0.75), (18.5, 12.5, 1.6))
return finish(name, bm, ORANGE)
def build_tower_beacons(name):
bm = bmesh.new()
for sgn in (-1, 1):
bmesh.ops.create_uvsphere(bm, u_segments=10, v_segments=6, radius=0.7,
matrix=Matrix.Translation((0, sgn * CABLE_Y, 226.9)))
ob = finish(name, bm, BEACON)
return smooth(ob)
def build_pier(name):
bm = bmesh.new()
# 교폭 방향으로 긴 둥근 직사각형 교각, 위로 약간 좁아진다
pts = []
rx, half = 15.0, 18.0
for k in range(13):
a = -math.pi / 2 + math.pi * k / 12
pts.append((rx * math.cos(a), half + rx * math.sin(a)))
for k in range(13):
a = math.pi / 2 + math.pi * k / 12
pts.append((rx * math.cos(a), -half + rx * math.sin(a)))
bot = [bm.verts.new((x * 1.06, y * 1.03, 0)) for x, y in pts]
top = [bm.verts.new((x, y, PIER_TOP)) for x, y in pts]
bm.faces.new(list(reversed(bot)))
bm.faces.new(top)
n = len(pts)
for i in range(n):
bm.faces.new((bot[i], bot[(i + 1) % n], top[(i + 1) % n], top[i]))
# 수면 근처의 넓은 기단
bmesh.ops.create_cone(bm, cap_ends=True, segments=40, radius1=1, radius2=1, depth=2.5,
matrix=Matrix.LocRotScale((0, 0, 1.25), None, (18.5, 36.0, 1)))
return finish(name, bm, CONCRETE)
def build_fender(name):
"""남쪽 주탑을 둘러싼 타원형 방현벽(펜더)."""
bm = bmesh.new()
seg = 64
ax, ay, t, h = 48.0, 72.0, 5.0, 9.0
outer_b, outer_t, inner_b, inner_t = [], [], [], []
for k in range(seg):
a = 2 * math.pi * k / seg
c, s = math.cos(a), math.sin(a)
outer_b.append(bm.verts.new((ax * c, ay * s, 0)))
outer_t.append(bm.verts.new((ax * c, ay * s, h)))
inner_b.append(bm.verts.new(((ax - t) * c, (ay - t) * s, 0)))
inner_t.append(bm.verts.new(((ax - t) * c, (ay - t) * s, h)))
for k in range(seg):
j = (k + 1) % seg
bm.faces.new((outer_b[k], outer_b[j], outer_t[j], outer_t[k]))
bm.faces.new((inner_b[j], inner_b[k], inner_t[k], inner_t[j]))
bm.faces.new((outer_t[k], outer_t[j], inner_t[j], inner_t[k]))
bm.faces.new((outer_b[j], outer_b[k], inner_b[k], inner_b[j]))
return finish(name, bm, CONCRETE_DK)
# ---------------------------------------------------------------- 보강 트러스와 노면
xs = [-DECK_END + i * PANEL for i in range(2 * NPANEL + 1)]
ZR_TOP = 0.6 # 상현재 윗면(노면 위로 올라와 연석 역할)
TC = (-1.2, ZR_TOP)
BC = (-8.25, -7.05)
def build_truss():
bm = bmesh.new()
for sgn in (-1, 1):
y = sgn * CABLE_Y
bm_sweep(bm, xs, rect(y - 0.55, y + 0.55, *TC))
bm_sweep(bm, xs, rect(y - 0.55, y + 0.55, *BC))
for i, x in enumerate(xs):
zr = road_z(x)
bm_beam(bm, (x, y, zr + BC[1] - 0.1), (x, y, zr + TC[0] + 0.1), 0.6, 0.6)
if i + 1 < len(xs):
x2 = xs[i + 1]
if i % 2 == 0:
a, b = (x, y, zr + BC[1]), (x2, y, road_z(x2) + TC[0])
else:
a, b = (x, y, zr + TC[0]), (x2, y, road_z(x2) + BC[1])
bm_beam(bm, a, b, 0.5, 0.55)
for i, x in enumerate(xs):
zr = road_z(x)
# 가로보
bm_beam(bm, (x, -CABLE_Y, zr - 1.15), (x, CABLE_Y, zr - 1.15), 0.7, 1.3)
bm_beam(bm, (x, -CABLE_Y, zr + BC[0] + 0.6), (x, CABLE_Y, zr + BC[0] + 0.6), 0.5, 0.5)
# 하부 수평 브레이싱(X 자)
if i + 1 < len(xs):
x2 = xs[i + 1]
z1, z2 = zr + BC[0] + 0.6, road_z(x2) + BC[0] + 0.6
bm_beam(bm, (x, -CABLE_Y, z1), (x2, CABLE_Y, z2), 0.45, 0.35)
bm_beam(bm, (x, CABLE_Y, z1), (x2, -CABLE_Y, z2), 0.45, 0.35)
# 난간: 상현재 위 기둥 + 손잡이
rx = [x for x in xs]
for sgn in (-1, 1):
y = sgn * (CABLE_Y + 0.25)
bm_sweep(bm, xs, rect(y - 0.1, y + 0.1, 1.2, 1.35))
for i in range(len(rx) - 1):
for f in (0.0, 0.5):
x = rx[i] + f * PANEL
bm_box(bm, (x, y, road_z(x) + 0.9), (0.15, 0.15, 0.7))
return finish('Deck_StiffeningTruss', bm, ORANGE)
def build_road():
bm = bmesh.new()
bm_sweep(bm, xs, rect(-13.2, 13.2, -0.55, 0.0))
return finish('Deck_Roadway', bm, ASPHALT)
def build_walks():
bm = bmesh.new()
for sgn in (-1, 1):
y0, y1 = sorted((sgn * 9.45, sgn * 13.15))
bm_sweep(bm, xs, rect(y0, y1, -0.1, 0.25))
# 가운데 이동식 분리대
bm_sweep(bm, xs, [(-0.3, -0.1), (0.3, -0.1), (0.15, 0.8), (-0.15, 0.8)])
return finish('Deck_Sidewalks_Median', bm, WALK)
# ---------------------------------------------------------------- 케이블과 행어
def cable_points(sgn_x):
"""한쪽 앵커리지에서 반대편 앵커리지까지의 케이블 중심선."""
pts = []
x = -ANCHOR_X - 7.0
while x <= ANCHOR_X + 7.0 + 1e-6:
xa = min(abs(x), ANCHOR_X)
z = cable_z(xa) if abs(x) <= ANCHOR_X else cable_z(ANCHOR_X) - (abs(x) - ANCHOR_X) * 0.6
pts.append((x, z))
x += 5.0
return pts
def suspender_xs():
out = []
k = 0.0
xmax = ANCHOR_X - 12.0
x = 0.0
xsus = []
while x < xmax:
if abs(x - TOWER_X) > 10.0:
xsus.append(x)
x += 2 * PANEL
for x in xsus:
out.append(x)
if x > 0:
out.append(-x)
return sorted(out)
def build_cables():
bm = bmesh.new()
prof = cable_points(1)
for sgn in (-1, 1):
y = sgn * CABLE_Y
bm_tube(bm, [(x, y, z) for x, z in prof], 0.55, seg=14)
return smooth(finish('Main_Cables', bm, CABLE))
def build_suspenders():
bm = bmesh.new()
for x in suspender_xs():
zc = cable_z(x)
zt = road_z(x) + ZR_TOP
for sgn in (-1, 1):
y = sgn * CABLE_Y
# 한 점에 두 가닥씩
for dx in (-0.3, 0.3):
bm_tube(bm, [(x + dx, y, zt - 0.05), (x + dx, y, zc)], 0.12, seg=6)
# 케이블 밴드
xa = abs(x)
dz = (cable_z(xa + 0.5) - cable_z(xa - 0.5)) * (1 if x >= 0 else -1)
d = Vector((1.0, 0, dz)).normalized()
c = Vector((x, y, zc))
bm_tube(bm, [c - d * 0.9, c + d * 0.9], 0.78, seg=12)
return finish('Suspender_Ropes', bm, CABLE)
# ---------------------------------------------------------------- 앵커리지와 파일론
ZA = road_z(ANCHOR_X)
def build_anchorage(name):
"""케이블을 묶어 두는 콘크리트 덩어리. 위로 갈수록 한 단씩 물러난다."""
bm = bmesh.new()
x0, x1 = -9.0, DECK_END - ANCHOR_X + 2.0
top = ZA + BC[0] - 0.05
L = x1 - x0
cx = x0 + L / 2
tiers = [(0.0, 10.0, L + 8.0, 58.0), (10.0, top - 9.0, L + 2.0, 52.0), (top - 9.0, top, L, 48.0)]
for a, b, sx, sy in tiers:
bm_box(bm, (cx, 0, (a + b) / 2 - 0.02), (sx, sy, b - a + 0.04))
# 옆면의 세로 버트레스와 띠
for sgn in (-1, 1):
for i in range(5):
x = x0 + 4.0 + (L - 8.0) * i / 4
bm_box(bm, (x, sgn * 26.4, 10.0 + (top - 20.0) / 2), (3.2, 1.2, top - 20.0))
bm_box(bm, (cx, sgn * 26.3, top - 10.0), (L + 3.0, 1.0, 1.6))
for fx in (x0 - 1.0, x1 + 1.0):
for i in range(5):
y = -20 + 10 * i
bm_box(bm, (fx + (0.4 if fx > cx else -0.4), y, 10.0 + (top - 20.0) / 2), (1.2, 3.2, top - 20.0))
bm_box(bm, (fx, 0, top - 10.0), (1.0, 53.0, 1.6))
return finish(name, bm, CONCRETE)
def build_pylon(name):
"""측경간 끝의 아르데코 콘크리트 파일론(케이블이 들어가는 곳)."""
bm = bmesh.new()
z0 = ZA + BC[0] - 0.1
tiers = [(z0, ZA + 24.0, 16.0, 9.0), (ZA + 24.0, ZA + 29.0, 13.5, 7.6), (ZA + 29.0, ZA + 33.0, 11.0, 6.2),
(ZA + 33.0, ZA + 35.5, 8.0, 4.8)]
yc = CABLE_Y + 1.2
for a, b, sx, sy in tiers:
bm_box(bm, (0, yc, (a + b) / 2 - 0.02), (sx, sy, b - a + 0.04))
for ox in (-5.0, 0.0, 5.0):
for fy in (-1, 1):
bm_box(bm, (ox, yc + fy * 4.62, (z0 + ZA + 22.0) / 2), (1.4, 0.4, ZA + 22.0 - z0))
for fx in (-1, 1):
bm_box(bm, (fx * 8.12, yc + ox * 0.55, (z0 + ZA + 22.0) / 2), (0.4, 1.4, ZA + 22.0 - z0))
return finish(name, bm, CONCRETE)
# ---------------------------------------------------------------- 가로등
def build_lamp():
bm = bmesh.new()
bm_tube(bm, [(0, 0, 0), (0, 0, 8.5)], 0.13, seg=8)
bm_tube(bm, [(0, 0, 8.4), (0, -1.6, 8.9)], 0.08, seg=6)
bm_box(bm, (0, 0, 0.3), (0.45, 0.45, 0.6))
ob = finish('Street_Lamp', bm, ORANGE)
bm2 = bmesh.new()
bm_box(bm2, (0, -1.9, 8.8), (0.5, 0.9, 0.3))
head = finish('Street_Lamp_Head', bm2, LAMP)
return ob, head
# ---------------------------------------------------------------- 조립
tower_s = build_tower('Tower_South')
tower_s.location.x = -TOWER_X
tower_n = duplicate(tower_s, loc=(TOWER_X, 0, 0), name='Tower_North')
beacon_s = build_tower_beacons('Tower_South_Beacons')
beacon_s.location.x = -TOWER_X
duplicate(beacon_s, loc=(TOWER_X, 0, 0), name='Tower_North_Beacons')
pier_s = build_pier('Pier_South')
pier_s.location.x = -TOWER_X
duplicate(pier_s, loc=(TOWER_X, 0, 0), name='Pier_North')
fender = build_fender('Pier_South_Fender')
fender.location.x = -TOWER_X
build_truss()
build_road()
build_walks()
build_cables()
build_suspenders()
anc = build_anchorage('Anchorage_North')
anc.location.x = ANCHOR_X
duplicate(anc, loc=(-ANCHOR_X, 0, 0), rot=(0, 0, math.pi), name='Anchorage_South')
pyl = build_pylon('Pylon_North_East')
pyl.location.x = ANCHOR_X + 6.0
for nm, loc, rz in (('Pylon_North_West', (ANCHOR_X + 6.0, 0, 0), math.pi),
('Pylon_South_East', (-ANCHOR_X - 6.0, 0, 0), 0.0),
('Pylon_South_West', (-ANCHOR_X - 6.0, 0, 0), math.pi)):
p = duplicate(pyl, loc=loc, name=nm)
if rz:
p.scale = (1, -1, 1)
pole, head = build_lamp()
lamp_x = []
x = -ANCHOR_X + 20.0
while x < ANCHOR_X - 15.0:
if abs(abs(x) - TOWER_X) > 14.0:
lamp_x.append(x)
x += 6 * PANEL
first = True
for x in lamp_x:
for sgn in (-1, 1):
loc = (x + 1.9, sgn * (CABLE_Y + 0.25), road_z(x) + ZR_TOP)
rot = (0, 0, 0 if sgn > 0 else math.pi)
if first:
pole.location, pole.rotation_euler = loc, rot
head.location, head.rotation_euler = loc, rot
first = False
else:
duplicate(pole, loc=loc, rot=rot)
duplicate(head, loc=loc, rot=rot)

다른 모형