본문으로 건너뛰기
3D 아틀리에
타워크레인 (해머헤드형) 3D 모형

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

이 모형에 대하여

타워크레인은 땅에 고정한 수직 마스트 꼭대기에 선회 장치를 얹고, 그 위에 수평으로 뻗은 지브를 달아 무거운 자재를 들어 옮기는 건설 기계다. 지브 아래를 오가는 트롤리가 훅의 반경을 바꾸고, 반대쪽 카운터지브의 콘크리트 균형추가 하중의 모멘트를 상쇄한다. 마스트는 같은 규격의 섹션을 볼트로 이어 쌓기 때문에 건물이 올라가는 데 맞춰 높이를 늘릴 수 있다.

탑헤드(캣헤드)에서 지브와 카운터지브로 펜던트 타이바를 내려 거는 이 형식은 옆에서 보면 망치처럼 생겨 해머헤드형이라 부른다. 노란색 격자 구조와 망치 모양 실루엣은 세계 여러 도시의 공사 현장에서 볼 수 있다.

이 모델은 높이 약 34m, 지브 길이 45m, 카운터지브 15m 규모의 일반적인 해머헤드형 크레인이 프리캐스트 콘크리트 벽판을 들어 올리는 장면을 재현했다. 콘크리트 기초, 내부 사다리가 있는 4개의 마스트 섹션, 선회 베어링과 운전실, 피라미드형 탑헤드, 삼각 단면 트러스 지브, 윈치와 전기함이 실린 카운터지브와 그 난간에 건 명판, 6장의 균형추 슬래브, 트롤리와 4가닥 로프에 매달린 훅 블록, 두 가닥 슬링으로 건 벽판을 담았다. 특정 제조사의 기종을 그대로 옮긴 것은 아니다.

만든 방법

두 점 사이에 각기둥을 세우는 strut 함수를 만들어 격자 부재 수백 개를 하나의 bmesh 에 모았고, 주재는 사각관, 가새와 난간은 12각 원관으로 구분했다. 마스트 섹션은 사다리와 플랜지 볼트까지 포함해 한 번만 만든 뒤 메시를 공유하는 링크 복제로 4단을 쌓았다. 지브는 패널 간격 2.5m 의 워런 트러스를 루프로 생성했으며 끝 10m 에서 높이가 1.8m 에서 1.0m 로 줄어든다. 훅은 원호를 따라가는 커브 관으로, 벽판의 연결 철근 고리와 슬링은 짧은 커브 관으로 만들었다. 용접부, 케이블 배선, 시브 내부, 운전실 내부 설비와 명판의 글자는 생략했고, 로프는 화면에서 보이도록 실제보다 조금 굵게 했다.

build.py

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

# 해머헤드형 타워크레인 (Hammerhead Tower Crane) — 건설 현장의 대표 장비.
# 콘크리트 기초 위 높이 약 20m 의 사각 격자 마스트에 선회 장치·운전실·탑헤드(캣헤드)를 얹고,
# 앞쪽으로 45m 삼각 단면 격자 지브, 뒤쪽으로 15m 카운터지브와 콘크리트 균형추를 달았다.
# 격자 부재는 두 점 사이에 각기둥을 세우는 함수(strut)로 bmesh 하나에 모아 만들고,
# 같은 모양의 마스트 섹션 4개는 메시를 공유하는 링크 복제로 쌓았다.
# 지브는 패널 간격 2.5m 의 워런 트러스이며 끝 10m 에서 높이가 줄어든다.
# 펜던트 타이바, 트롤리와 훅 블록, 와이어로프, 윈치, 난간, 카운터지브 명판, 항공 장애등과
# 훅에 매달려 올라가는 프리캐스트 콘크리트 벽판까지 코드로만 표현했다.
from atelier import *
SHOT = {"azimuth": -86, "elevation": 6, "lens": 70, "light": "day", "background": "#2b3442"}
# ── 재질 ─────────────────────────────────────────────
YELLOW = mat('Crane_Yellow_Paint', '#e0a516', 0.42, 0.0)
CONCRETE = mat('Foundation_Concrete', '#a09b92', 0.92, 0.0)
BALLAST = mat('Counterweight_Concrete', '#85827b', 0.88, 0.0)
STEEL = mat('Dark_Machined_Steel', '#3b3f45', 0.38, 0.85)
GALV = mat('Galvanized_Grating', '#8e949a', 0.5, 0.75)
CABPAINT = mat('Cab_Light_Grey', '#dcdad3', 0.38, 0.0)
GLASS = glass('Cab_Tinted_Glass', '#6f8e9e', 0.04)
ROPE = mat('Wire_Rope', '#52555a', 0.35, 0.9)
RED = mat('Hook_Red_Paint', '#b3312a', 0.42, 0.0)
LAMP = mat('Aviation_Red_Lamp', '#d82a1e', 0.3, 0.0, emit='#ff2a14', emit_strength=6.0)
CABINET = mat('Cabinet_Grey', '#6b737b', 0.5, 0.2)
BOARD = mat('Counter_Jib_Name_Board', '#e4e0d4', 0.55, 0.0)
PRECAST = mat('Precast_Wall_Panel', '#b9b4aa', 0.9, 0.0)
BOARD_BAND = mat('Name_Board_Blue_Band', '#2c5b9a', 0.5, 0.0)
# ── 격자 부재 도우미 ───────────────────────────────────
def _frame(d):
ref = Vector((0, 0, 1)) if abs(d.z) < 0.9 else Vector((1, 0, 0))
u = d.cross(ref).normalized()
v = d.cross(u).normalized()
return u, v
def strut(bm, p0, p1, r, sides=12, square=False):
"""bm 안에 p0→p1 각기둥 하나(반지름 r, square=True 면 한 변 2r 의 각관)."""
p0, p1 = Vector(p0), Vector(p1)
d = (p1 - p0)
if d.length < 1e-6:
return
d.normalize()
u, v = _frame(d)
n = 4 if square else sides
rr = r * math.sqrt(2) if square else r
off = math.pi / 4 if square else 0.0
나머지 308줄 펼치기
ring0, ring1 = [], []
for k in range(n):
a = off + 2 * math.pi * k / n
o = (u * math.cos(a) + v * math.sin(a)) * rr
ring0.append(bm.verts.new(p0 + o))
ring1.append(bm.verts.new(p1 + o))
for k in range(n):
bm.faces.new((ring0[k], ring0[(k + 1) % n], ring1[(k + 1) % n], ring1[k]))
bm.faces.new(list(reversed(ring0)))
bm.faces.new(ring1)
def mesh_from(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)
# ── 주요 치수 ─────────────────────────────────────────
ZF = 1.5 # 기초 윗면
MW = 1.9 # 마스트 주재 중심 간격
MH = 5.0 # 마스트 섹션 높이
NSEC = 4
JH = 1.8 # 지브 높이
JW = 0.8 # 지브 하현재 반폭
J0 = 1.45 # 지브 뿌리 x
PANEL = 2.5
NPAN = 18
JTIP = J0 + PANEL * NPAN
# ── 기초 ─────────────────────────────────────────────
box('Foundation_Block', (8.0, 8.0, ZF), (0, 0, 0), material=CONCRETE, base=True)
box('Foundation_Plinth', (3.0, 3.0, 0.25), (0, 0, ZF), material=CONCRETE, base=True)
h = MW / 2
for i, (sx, sy) in enumerate(((1, 1), (-1, 1), (-1, -1), (1, -1))):
box(f'Mast_Anchor_Plate_{i+1}', (0.6, 0.6, 0.08), (sx * h, sy * h, ZF + 0.25), material=STEEL, base=True)
cylinder(f'Mast_Anchor_Boss_{i+1}', 0.2, 0.3, (sx * h, sy * h, ZF + 0.33), material=YELLOW, seg=12)
ZB = ZF + 0.63 # 첫 섹션 시작 (기초 위 앵커 상단)
# ── 마스트 섹션 (하나 만들고 링크 복제) ─────────────────
bm = bmesh.new()
C = [(h, h), (-h, h), (-h, -h), (h, -h)]
for x, y in C:
strut(bm, (x, y, 0), (x, y, MH), 0.1, square=True)
for f in range(4):
a, b = C[f], C[(f + 1) % 4]
for z in (0.0, MH / 2):
strut(bm, (a[0], a[1], z + 0.06), (b[0], b[1], z + 0.06), 0.045, sides=12)
strut(bm, (a[0], a[1], 0.06), (b[0], b[1], MH / 2), 0.04, sides=12)
strut(bm, (b[0], b[1], MH / 2), (a[0], a[1], MH), 0.04, sides=12)
# 섹션 결합 플랜지(주재 끝)
for x, y in C:
strut(bm, (x, y, -0.02), (x, y, 0.12), 0.15, square=True)
# 플랜지 볼트
for x, y in C:
for bx in (-0.125, 0.125):
for by in (-0.125, 0.125):
strut(bm, (x + bx, y + by, -0.07), (x + bx, y + by, 0.17), 0.028, sides=12)
# 내부 사다리
ly = -h + 0.42
for sx in (-0.22, 0.22):
strut(bm, (sx, ly, 0), (sx, ly, MH), 0.025, sides=12)
for k in range(16):
z = 0.15 + k * 0.31
strut(bm, (-0.22, ly, z), (0.22, ly, z), 0.016, sides=6)
# 사다리 지지대
for z in (1.2, 3.7):
strut(bm, (-0.22, ly, z), (-0.22, -h, z), 0.02, sides=6)
strut(bm, (0.22, ly, z), (0.22, -h, z), 0.02, sides=6)
section = mesh_from('Mast_Section_01', bm, YELLOW, (0, 0, ZB))
for k in range(1, NSEC):
duplicate(section, loc=(0, 0, ZB + k * MH), name=f'Mast_Section_{k+1:02d}')
ZTOP = ZB + MH * NSEC
# 마스트 꼭대기 테두리
bm = bmesh.new()
for f in range(4):
a, b = C[f], C[(f + 1) % 4]
strut(bm, (a[0], a[1], 0), (b[0], b[1], 0), 0.06, sides=12)
mesh_from('Mast_Top_Ring', bm, YELLOW, (0, 0, ZTOP - 0.06))
# ── 선회 장치 ────────────────────────────────────────
z = ZTOP
box('Slewing_Support_Frame', (2.4, 2.4, 0.5), (0, 0, z), material=YELLOW, base=True)
z += 0.5
cylinder('Slewing_Ring_Bearing', 1.25, 0.4, (0, 0, z), material=STEEL, seg=48)
z += 0.4
box('Slewing_Turntable', (3.2, 3.0, 1.1), (0, 0, z), material=YELLOW, base=True)
ZP = z + 1.1 # 선회 플랫폼 윗면
ZJ = ZP - 0.45 # 지브 하현재 중심 높이
APEX = ZP + 8.2 # 탑헤드 꼭대기
for i, a in enumerate((0.0, math.pi)):
box(f'Slewing_Gear_Motor_{i+1}', (0.35, 0.35, 0.7), (0.9 * math.cos(a + 0.6), 0.9 * math.sin(a + 0.6), z - 0.45),
material=STEEL, base=True)
# 플랫폼 난간
bm = bmesh.new()
px, py = 1.6, 1.5
pts = [(px, py), (-px, py), (-px, -py), (px, -py)]
for i in range(4):
a, b = pts[i], pts[(i + 1) % 4]
for hz in (0.5, 1.05):
strut(bm, (a[0], a[1], ZP + hz), (b[0], b[1], ZP + hz), 0.025, sides=12)
strut(bm, (a[0], a[1], ZP), (a[0], a[1], ZP + 1.05), 0.03, sides=12)
mesh_from('Turntable_Handrail', bm, YELLOW)
# ── 운전실 ──────────────────────────────────────────
cy0, cy1 = -1.5, -3.2
cx0, cx1 = -0.4, 1.9
cz0, cz1 = ZP - 1.7, ZP + 0.85
cyc, cxc = (cy0 + cy1) / 2, (cx0 + cx1) / 2
box('Cab_Body', (cx1 - cx0, cy0 - cy1, cz1 - cz0), (cxc, cyc, cz0), material=CABPAINT, base=True)
box('Cab_Roof', (cx1 - cx0 + 0.1, cy0 - cy1 + 0.1, 0.12), (cxc, cyc, cz1), material=YELLOW, base=True)
box('Cab_Floor_Bracket', (cx1 - cx0, 0.5, 0.35), (cxc, cy0 + 0.1, cz0 - 0.2), material=YELLOW, base=True)
# 창: 앞(+X), 옆(-Y), 뒤(-X)
box('Cab_Front_Window', (0.04, cy0 - cy1 - 0.3, cz1 - cz0 - 0.5), (cx1 + 0.01, cyc, cz0 + 0.3), material=GLASS, base=True)
box('Cab_Side_Window', (cx1 - cx0 - 0.5, 0.04, 1.3), (cxc + 0.1, cy1 - 0.01, cz0 + 1.0), material=GLASS, base=True)
box('Cab_Side_Door_Window', (0.04, 0.6, 0.9), (cx0 - 0.01, cyc - 0.3, cz0 + 1.3), material=GLASS, base=True)
box('Cab_Window_Mullion', (0.08, 0.06, cz1 - cz0 - 0.5), (cx1 + 0.04, cyc, cz0 + 0.3), material=CABPAINT, base=True)
box('Cab_Wiper_Arm', (0.03, 0.03, 0.8), (cx1 + 0.05, cyc + 0.4, cz0 + 0.6), rot=(0.4, 0, 0), material=STEEL, base=True)
box('Cab_AC_Unit', (0.6, 0.5, 0.35), (cx0 + 0.5, cyc, cz1 + 0.12), material=CABINET, base=True)
# ── 탑헤드 (캣헤드) ───────────────────────────────────
TB, TT = 1.25, 0.35
bm = bmesh.new()
base_pts = [(TB, TB), (-TB, TB), (-TB, -TB), (TB, -TB)]
def lerp2(p, t):
s = TB + (TT - TB) * t
return (p[0] / TB * s, p[1] / TB * s)
for p in base_pts:
strut(bm, (p[0], p[1], ZP), (*lerp2(p, 1.0), APEX), 0.09, square=True)
levels = [0.0, 0.33, 0.62, 0.86]
for f in range(4):
a, b = base_pts[f], base_pts[(f + 1) % 4]
for li, t in enumerate(levels):
za = ZP + (APEX - ZP) * t + (0.1 if t == 0 else 0)
strut(bm, (*lerp2(a, t), za), (*lerp2(b, t), za), 0.045)
if li + 1 < len(levels):
t2 = levels[li + 1]
zb = ZP + (APEX - ZP) * t2
if li % 2 == 0:
strut(bm, (*lerp2(a, t), za), (*lerp2(b, t2), zb), 0.04)
else:
strut(bm, (*lerp2(b, t), za), (*lerp2(a, t2), zb), 0.04)
mesh_from('Tower_Head_Lattice', bm, YELLOW)
box('Tower_Head_Cap', (1.0, 1.0, 0.5), (0, 0, APEX - 0.1), material=YELLOW, base=True)
box('Tower_Head_Pendant_Lug', (1.3, 0.3, 0.3), (0, 0, APEX + 0.4), material=YELLOW, base=True)
cylinder('Tower_Head_Sheave', 0.32, 0.18, (0.0, 0.0, APEX + 0.95), rot=(math.pi / 2, 0, 0), material=STEEL, seg=24, base=False)
cylinder('Aviation_Lamp_Mount', 0.05, 0.25, (0, 0, APEX + 0.7), material=STEEL, seg=8)
sphere('Aviation_Lamp_Top', 0.13, (0, 0, APEX + 1.3), material=LAMP, seg=12, rings=8)
cylinder('Aviation_Lamp_Top_Post', 0.04, 0.35, (0, 0, APEX + 0.95), material=STEEL, seg=8)
# ── 지브 (삼각 단면 워런 트러스) ──────────────────────────
def jib_h(x):
t = (x - (JTIP - 10.0)) / 10.0
return JH if t <= 0 else JH + (1.0 - JH) * min(t, 1.0)
bm = bmesh.new()
bot = [J0 + i * PANEL for i in range(NPAN + 1)]
tops = [J0] + [J0 + (i + 0.5) * PANEL for i in range(NPAN)] + [JTIP]
tp = [Vector((x, 0, ZJ + jib_h(x))) for x in tops]
for s in (-1, 1):
y = s * JW
strut(bm, (J0 - 0.15, y, ZJ), (JTIP, y, ZJ), 0.11, square=True) # 하현재
strut(bm, (J0, y, ZJ), tp[0], 0.06) # 뿌리 수직재
strut(bm, (JTIP, y, ZJ), tp[-1], 0.05) # 끝 수직재
for i in range(NPAN):
strut(bm, (bot[i], y, ZJ), tp[i + 1], 0.045)
strut(bm, tp[i + 1], (bot[i + 1], y, ZJ), 0.045)
for i in range(len(tp) - 1): # 상현재
strut(bm, tp[i], tp[i + 1], 0.09, square=True)
for i, x in enumerate(bot): # 하부 가로재·대각재
strut(bm, (x, -JW, ZJ + 0.04), (x, JW, ZJ + 0.04), 0.04)
if i < NPAN:
s = 1 if i % 2 == 0 else -1
strut(bm, (x, -s * JW, ZJ + 0.04), (bot[i + 1], s * JW, ZJ + 0.04), 0.035)
mesh_from('Jib_Lattice', bm, YELLOW)
box('Jib_Root_Pin_Plate', (0.25, 2.0, 0.7), (J0 - 0.05, 0, ZJ - 0.35), material=YELLOW, base=True)
box('Jib_Tip_Sheave_Housing', (0.4, 1.8, 0.4), (JTIP + 0.15, 0, ZJ - 0.15), material=YELLOW, base=True)
sphere('Aviation_Lamp_Jib_Tip', 0.12, (JTIP + 0.05, 0, ZJ + 1.0 + 0.2), material=LAMP, seg=12, rings=8)
# 지브 펜던트(타이바) — 탑헤드에서 상현재 두 곳으로
bm = bmesh.new()
for xa in (21.45, 38.95):
za = ZJ + jib_h(xa)
for s in (-1, 1):
strut(bm, (TT * 0.6, s * 0.12, APEX + 0.55), (xa, s * 0.12, za + 0.05), 0.055, sides=12)
strut(bm, (xa, -0.2, za), (xa, 0.2, za), 0.08, sides=12)
mesh_from('Jib_Pendant_Bars', bm, YELLOW)
# ── 카운터지브 ──────────────────────────────────────
CX0, CX1 = -1.5, -17.0
CW = 1.1
cxm, clen = (CX0 + CX1) / 2, CX0 - CX1
for s in (-1, 1):
box(f'Counter_Jib_Girder_{"L" if s > 0 else "R"}', (clen, 0.25, 0.6), (cxm, s * CW, ZJ - 0.3), material=YELLOW, base=True)
box('Counter_Jib_Walkway', (CX0 - (-13.3), 2.0, 0.05), ((CX0 - 13.3) / 2, 0, ZJ + 0.22), material=GALV, base=True)
for k in range(6):
x = CX0 - 0.6 - k * 2.3
box(f'Counter_Jib_Crossbeam_{k+1}', (0.2, 2.2, 0.25), (x, 0, ZJ - 0.05), material=YELLOW, base=True)
# 난간
bm = bmesh.new()
for s in (-1, 1):
y = s * (CW + 0.05)
zt = ZJ + 0.3
for k in range(11):
x = CX0 - 0.3 - k * 1.5
strut(bm, (x, y, zt), (x, y, zt + 1.05), 0.03)
for hz in (0.5, 1.05):
strut(bm, (CX0 - 0.3, y, zt + hz), (CX1 + 0.5, y, zt + hz), 0.025)
mesh_from('Counter_Jib_Handrail', bm, YELLOW)
# 균형추 슬래브
box('Counterweight_Saddle', (3.6, 2.6, 0.12), (-15.15, 0, ZJ + 0.3), material=YELLOW, base=True)
slab = box('Counterweight_Slab_1', (0.56, 1.9, 3.3), (-13.65, 0, ZJ - 2.2), material=BALLAST, base=True)
bevel(slab, 0.03, 1)
for k in range(1, 6):
duplicate(slab, loc=(-13.65 - k * 0.6, 0, ZJ - 2.2), name=f'Counterweight_Slab_{k+1}')
box('Counterweight_Hanger_Bar', (3.6, 0.15, 0.15), (-15.15, 0, ZJ + 0.42), material=STEEL, base=True)
sphere('Aviation_Lamp_Counter_Jib', 0.12, (CX1 + 0.2, CW + 0.05, ZJ + 1.55), material=LAMP, seg=12, rings=8)
# 호이스트 윈치
zw = ZJ + 0.27
box('Hoist_Winch_Frame', (1.6, 1.8, 0.3), (-9.5, 0, zw), material=YELLOW, base=True)
cylinder('Hoist_Winch_Drum', 0.45, 1.3, (-9.5, 0, zw + 0.3 + 0.5), rot=(math.pi / 2, 0, 0), material=STEEL, seg=32, base=False)
for s in (-1, 1):
cylinder(f'Hoist_Winch_Drum_Flange_{"L" if s > 0 else "R"}', 0.58, 0.06, (-9.5, s * 0.68, zw + 0.8),
rot=(math.pi / 2, 0, 0), material=YELLOW, seg=32, base=False)
box(f'Hoist_Winch_Bearing_{"L" if s > 0 else "R"}', (0.5, 0.15, 0.85), (-9.5, s * 0.8, zw + 0.3), material=YELLOW, base=True)
cylinder('Hoist_Motor', 0.3, 0.9, (-10.6, 0.55, zw + 0.3 + 0.35), rot=(0, math.pi / 2, 0), material=CABINET, seg=24, base=False)
box('Hoist_Gearbox', (0.5, 0.5, 0.55), (-10.6, -0.4, zw + 0.3), material=STEEL, base=True)
box('Electrical_Cabinet', (1.2, 0.7, 1.5), (-6.0, 0.55, zw), material=CABINET, base=True)
box('Electrical_Cabinet_2', (0.9, 0.6, 1.2), (-4.4, 0.6, zw), material=CABINET, base=True)
# 카운터지브 펜던트
bm = bmesh.new()
for s in (-1, 1):
strut(bm, (-TT * 0.6, s * 0.12, APEX + 0.55), (-13.0, s * CW, ZJ + 0.3), 0.055, sides=12)
mesh_from('Counter_Jib_Pendant_Bars', bm, YELLOW)
for s in (-1, 1):
box(f'Counter_Jib_Pendant_Lug_{"L" if s > 0 else "R"}', (0.4, 0.2, 0.3), (-13.0, s * CW, ZJ + 0.25), material=YELLOW, base=True)
# 호이스트 로프: 윈치 → 탑헤드 시브 → 지브 끝
curve_tube('Hoist_Rope_Upper', [(-9.5, 0.0, zw + 1.25), (-0.3, 0, APEX + 0.95), (0.3, 0, APEX + 0.7)],
radius=0.03, material=ROPE, resolution=1, bevel_res=2)
# ── 트롤리와 훅 블록 ─────────────────────────────────
TX = 17.0
ZH = 13.5 # 훅 블록 위 높이
for s in (-1, 1):
box(f'Trolley_Side_Plate_{"L" if s > 0 else "R"}', (1.8, 0.08, 1.15), (TX, s * (JW + 0.2), ZJ - 0.85), material=YELLOW, base=True)
for dx in (-0.6, 0.6):
cylinder(f'Trolley_Wheel', 0.14, 0.08, (TX + dx, s * JW, ZJ + 0.11 + 0.14), rot=(math.pi / 2, 0, 0),
material=STEEL, seg=16, base=False)
cylinder(f'Trolley_Axle', 0.03, 0.25, (TX + dx, s * (JW + 0.08), ZJ + 0.25), rot=(math.pi / 2, 0, 0),
material=STEEL, seg=8, base=False)
box('Trolley_Cross_Frame', (1.8, 2.08, 0.3), (TX, 0, ZJ - 0.85), material=YELLOW, base=True)
for dx in (-0.35, 0.35):
cylinder('Trolley_Sheave', 0.25, 0.12, (TX + dx, 0, ZJ - 0.85), rot=(math.pi / 2, 0, 0), material=STEEL, seg=20, base=False)
# 로프 4가닥
for dx in (-0.25, 0.25):
for dy in (-0.12, 0.12):
cylinder('Hook_Rope_Fall', 0.03, (ZJ - 0.85) - (ZH + 1.0), (TX + dx, dy, ZH + 1.0), material=ROPE, seg=12)
# 로프: 지브 하부를 따라 트롤리로
curve_tube('Trolley_Rope_Rear', [(J0 + 0.2, 0.0, ZJ - 0.3), (TX - 0.9, 0.0, ZJ - 0.6)], radius=0.02, material=ROPE, resolution=1, bevel_res=2)
curve_tube('Trolley_Rope_Front', [(TX + 0.9, 0.0, ZJ - 0.6), (JTIP + 0.1, 0.0, ZJ - 0.05)], radius=0.02, material=ROPE, resolution=1, bevel_res=2)
box('Hook_Block_Body', (0.95, 0.55, 1.1), (TX, 0, ZH - 0.1), material=YELLOW, base=True)
for s in (-1, 1):
cylinder('Hook_Block_Sheave_Cover', 0.34, 0.06, (TX, s * 0.3, ZH + 0.58), rot=(math.pi / 2, 0, 0), material=RED, seg=24, base=False)
box('Hook_Block_Stripe', (0.97, 0.57, 0.18), (TX, 0, ZH + 0.15), material=RED, base=True)
cylinder('Hook_Swivel', 0.12, 0.5, (TX, 0, ZH - 0.55), material=STEEL, seg=16)
cylinder('Hook_Nut', 0.2, 0.12, (TX, 0, ZH - 0.2), material=STEEL, seg=6)
# 훅: 원호를 따라가는 관 (XZ 평면)
cx, cz, R = TX + 0.28, ZH - 1.15, 0.32
hook_pts = [(TX, 0, ZH - 0.55), (TX - 0.04, 0, ZH - 0.85)] + \
[(cx + R * math.cos(math.radians(a)), 0, cz + R * math.sin(math.radians(a))) for a in range(180, 390, 18)]
hk = curve_tube('Hook', hook_pts, radius=0.085, material=STEEL, resolution=4, bevel_res=3)
box('Hook_Safety_Latch', (0.3, 0.04, 0.05), (TX + 0.45, 0, ZH - 0.95), rot=(0, 0.9, 0), material=RED, base=True)
# ── 카운터지브 명판 (난간 바깥에 거는 대형 광고판) ─────────────────
for s_ in (-1, 1):
side = "L" if s_ > 0 else "R"
y = s_ * (CW + 0.13)
box(f'Name_Board_{side}', (11.0, 0.06, 2.8), (-7.9, y, ZJ - 0.25), material=BOARD, base=True)
box(f'Name_Board_Band_{side}', (11.0, 0.02, 0.5), (-7.9, y + s_ * 0.04, ZJ + 0.15), material=BOARD_BAND, base=True)
box(f'Name_Board_Band_Top_{side}', (11.0, 0.02, 0.2), (-7.9, y + s_ * 0.04, ZJ + 2.2), material=BOARD_BAND, base=True)
for k in range(6):
box(f'Name_Board_Bracket_{side}_{k+1}', (0.1, 0.12, 2.8), (-2.6 - k * 2.12, s_ * (CW + 0.05), ZJ - 0.25), material=YELLOW, base=True)
# ── 훅에 매단 프리캐스트 콘크리트 벽판 ───────────────────────────
HX, HZ = cx, cz - R + 0.05 # 훅 안쪽 바닥(슬링이 걸리는 점)
PW, PH, PT = 8.0, 3.2, 0.16
PZ = HZ - 2.6 - PH # 벽판 아래면
panel = box('Precast_Wall_Panel', (PW, PT, PH), (HX, 0, PZ), material=PRECAST, base=True)
bevel(panel, 0.015, 1)
for i, ax in enumerate((-2.0, 2.0)):
torus(f'Panel_Lifting_Loop_{i+1}', 0.12, 0.025, (HX + ax, 0, PZ + PH + 0.04), rot=(math.pi / 2, 0, 0), material=STEEL, seg=16, rseg=6)
curve_tube(f'Lifting_Sling_{i+1}', [(HX, 0, HZ), (HX + ax, 0, PZ + PH + 0.14)], radius=0.028, material=ROPE, resolution=1, bevel_res=2)
# 벽판 위아래 가장자리에 튀어나온 연결 철근 고리
for i in range(6):
x = HX - PW / 2 + 0.6 + i * (PW - 1.2) / 5
curve_tube(f'Panel_Rebar_Loop_{i+1}', [(x - 0.12, 0, PZ + 0.02), (x - 0.12, 0, PZ - 0.14), (x + 0.12, 0, PZ - 0.14), (x + 0.12, 0, PZ + 0.02)],
radius=0.012, material=ROPE, resolution=1, bevel_res=1)

다른 모형