
끌어서 회전, 휠·두 손가락으로 확대
이 모형에 대하여
타워크레인은 땅에 고정한 수직 마스트 꼭대기에 선회 장치를 얹고, 그 위에 수평으로 뻗은 지브를 달아 무거운 자재를 들어 옮기는 건설 기계다. 지브 아래를 오가는 트롤리가 훅의 반경을 바꾸고, 반대쪽 카운터지브의 콘크리트 균형추가 하중의 모멘트를 상쇄한다. 마스트는 같은 규격의 섹션을 볼트로 이어 쌓기 때문에 건물이 올라가는 데 맞춰 높이를 늘릴 수 있다.
탑헤드(캣헤드)에서 지브와 카운터지브로 펜던트 타이바를 내려 거는 이 형식은 옆에서 보면 망치처럼 생겨 해머헤드형이라 부른다. 노란색 격자 구조와 망치 모양 실루엣은 세계 여러 도시의 공사 현장에서 볼 수 있다.
이 모델은 높이 약 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, vdef 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 = 4JH = 1.8 # 지브 높이JW = 0.8 # 지브 하현재 반폭J0 = 1.45 # 지브 뿌리 xPANEL = 2.5NPAN = 18JTIP = 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 / 2for 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.42for 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 = ZTOPbox('Slewing_Support_Frame', (2.4, 2.4, 0.5), (0, 0, z), material=YELLOW, base=True)z += 0.5cylinder('Slewing_Ring_Bearing', 1.25, 0.4, (0, 0, z), material=STEEL, seg=48)z += 0.4box('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.5pts = [(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.2cx0, cx1 = -0.4, 1.9cz0, cz1 = ZP - 1.7, ZP + 0.85cyc, cxc = (cy0 + cy1) / 2, (cx0 + cx1) / 2box('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.35bm = 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.0CW = 1.1cxm, clen = (CX0 + CX1) / 2, CX0 - CX1for 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.27box('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.0ZH = 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.32hook_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.16PZ = 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)






