본문으로 건너뛰기
3D 아틀리에
석유 펌프잭 (빔 펌핑 유닛) 3D 모형

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

이 모형에 대하여

펌프잭은 스스로 분출하지 않는 유정에서 원유를 끌어올리는 지상 구동 장치다. 전동기의 회전을 감속기와 크랭크가 받아 워킹빔을 시소처럼 위아래로 흔들고, 빔 앞 끝의 말머리에 매달린 로드가 수백에서 수천 미터 아래의 플런저 펌프를 왕복시킨다. 고개를 끄덕이는 듯한 움직임 때문에 '노딩 동키', 말머리 모양 때문에 '호스헤드 펌프'라고도 불린다.

이 형식은 크랭크 끝에 무거운 평형추를 달아 로드와 유체의 무게를 상쇄하는 '크랭크 균형형'으로, 20세기 초 미국 유전에서 자리 잡은 뒤 지금까지 육상 유전에서 가장 흔한 인공채유 장비로 쓰인다. 샘슨 포스트라 부르는 A형 철골 위에 빔의 피벗이 놓이고, 뒤쪽 이퀄라이저에서 두 개의 피트먼 암이 크랭크로 내려가는 구성이 공통된 골격이다.

이 모델은 길이 약 13m, 높이 약 8m의 중형급 유닛을 실제 크기로 재현했다. 콘크리트 기초와 베이스 형강, 샘슨 포스트와 사다리, 말머리와 와이어 브라이들, 감속기와 평형추, 벨트 가드와 전동기, 유정 헤드와 제어반까지 한 대의 펌프잭을 이루는 주요 부품을 모두 갖췄고, 평형추가 감속기 위로 올라선 한 순간의 자세로 멈춰 세웠다.

만든 방법

크랭크 각도를 정한 뒤, 피트먼 길이가 일정하다는 조건을 이분법으로 풀어 워킹빔의 기울기를 계산했다. 말머리 앞면은 빔 피벗을 중심으로 한 원호여서, 와이어를 그 원호를 따라 감은 뒤 수직 접선으로 내려 폴리시드 로드가 항상 유정 중심 위에 오도록 했다. 말머리·감속기 케이스·크랭크·평형추·벨트 가드는 측면 프로필(감속기와 가드는 원들의 볼록 껍질)을 돌출해 만들었고, I형강 빔과 샘슨 포스트 다리·가새·피트먼은 두 점을 잇는 각재 함수로 배치했다. 볼트·리벳·사다리 가로대·모터 냉각핀은 메시를 공유하는 연결 복제로 반복했다. 감속기 내부 기어, 벨트 가드 안의 V벨트, 지하의 로드 스트링은 생략했고 도장 색은 실제 사례를 참고해 단순화했다.

build.py

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

# 석유 펌프잭 (빔 펌핑 유닛, 크랭크 균형형 · 중형급)
# 유정 위에서 고개를 끄덕이며 원유를 끌어올리는 '말머리' 펌프. 실제 크기(길이 약 12m, 높이 약 8m)로 만들었다.
# 동작 자세를 수식으로 맞췄다: 크랭크 각도를 정하면 피트먼 길이가 일정하다는 조건을
# 이분법으로 풀어 워킹빔의 기울기를 구하고, 말머리 원호(빔 피벗 중심)에 감긴 와이어가
# 접선으로 내려와 폴리시드 로드를 정확히 유정 중심 위에 매달도록 배치했다.
# 말머리·크랭크·평형추는 측면 프로필을 돌출(extrude)했고, I형강 빔·샘슨 포스트 다리·피트먼은
# 두 점 사이를 잇는 각재(strut)로, 사다리 가로대·볼트·모터 냉각핀은 연결 복제로 반복했다.
import math
from atelier import *
SHOT = {"azimuth": -58, "elevation": 12, "lens": 50, "light": "golden", "background": "#26303d"}
# ---------------------------------------------------------------- 재질
BLUE = mat("Paint_MachineryBlue", "#2d5378", rough=0.45, metal=0.15)
YELLOW = mat("Paint_SafetyYellow", "#d9a21c", rough=0.42, metal=0.1)
STEEL = mat("Steel_Dark", "#3b3e42", rough=0.45, metal=0.75)
GALV = mat("Steel_Galvanized", "#9da3a6", rough=0.4, metal=0.8)
CHROME = mat("Chrome_PolishedRod", "#d3d7da", rough=0.12, metal=1.0)
ROPE = mat("WireRope", "#5b5d60", rough=0.55, metal=0.8)
CONCRETE = mat("Concrete", "#8e8c88", rough=0.92)
MOTOR = mat("Paint_MotorGrey", "#5f676e", rough=0.5, metal=0.2)
RED = mat("Paint_ValveRed", "#a3291f", rough=0.45, metal=0.1)
RUBBER = mat("Rubber_VBelt", "#2a2a2a", rough=0.8)
WELL = mat("Paint_WellheadGreen", "#55654a", rough=0.5, metal=0.25)
# ---------------------------------------------------------------- 주요 치수(m)
PAD_H = 0.30 # 콘크리트 기초 두께
SKID_H = 0.35 # 베이스 I형강 높이
SK = PAD_H + SKID_H # 베이스 윗면
A = 3.8 # 피벗 ~ 말머리 원호 반지름
C = 3.4 # 피벗 ~ 이퀄라이저
XS, ZS = -A, 6.4 # 새들 베어링(빔 피벗) — 유정이 x=0 에 오도록
XG, ZC = -7.3, 3.0 # 감속기 저속축(크랭크축)
RP = 1.55 # 크랭크 핀 반지름
L_PIT = 3.6 # 피트먼 길이
PHI = math.radians(156) # 크랭크 각도(뒤쪽 위) → 평형추가 감속기 위로 올라선 자세
YP = 1.12 # 피트먼 좌우 위치
def solve_beam():
kx, kz = XG + RP * math.cos(PHI), ZC + RP * math.sin(PHI)
def f(b):
ex, ez = XS - C * math.cos(b), ZS - C * math.sin(b)
return math.hypot(ex - kx, ez - kz) - L_PIT
lo, hi = -0.6, 0.6
for _ in range(60):
mid = (lo + hi) / 2
나머지 404줄 펼치기
if f(lo) * f(mid) <= 0:
hi = mid
else:
lo = mid
return (lo + hi) / 2
BETA = solve_beam()
def empty(name, loc, rot=(0, 0, 0)):
e = bpy.data.objects.new(name, None)
e.empty_display_size = 0.5
link(e)
e.location = loc
e.rotation_euler = rot
return e
def strut(name, p1, p2, w, d, material):
"""p1→p2 를 잇는 각재(단면 w×d)."""
p1, p2 = Vector(p1), Vector(p2)
v = p2 - p1
ob = box(name, (v.length, w, d), loc=(p1 + p2) / 2, material=material)
ob.rotation_euler = v.to_track_quat('X', 'Z').to_euler()
bevel(ob, min(w, d) * 0.12, 2)
return ob
def rod(name, p1, p2, r, material, seg=16):
p1, p2 = Vector(p1), Vector(p2)
v = p2 - p1
ob = cylinder(name, r=r, h=v.length, loc=(p1 + p2) / 2, material=material, seg=seg, base=False)
ob.rotation_euler = v.to_track_quat('Z', 'Y').to_euler()
return ob
def ycyl(name, r, y0, y1, x, z, material, seg=64, parent=None):
"""Y 축 방향 원통(y0~y1)."""
ob = cylinder(name, r=r, h=y1 - y0, loc=(x, (y0 + y1) / 2, z), rot=(math.pi / 2, 0, 0),
material=material, seg=seg, base=False)
if parent:
ob.parent = parent
return ob
def ibeam(name, x0, x1, h, bf, tw, tf, y=0.0, z=0.0, material=None):
"""X 방향 I형강(x0~x1), 단면 중심 (y, z)."""
hh, bb, ww = h / 2, bf / 2, tw / 2
pts = [(-bb, -hh), (bb, -hh), (bb, -hh + tf), (ww, -hh + tf), (ww, hh - tf), (bb, hh - tf),
(bb, hh), (-bb, hh), (-bb, hh - tf), (-ww, hh - tf), (-ww, -hh + tf), (-bb, -hh + tf)]
ob = extrude_profile(name, pts, x1 - x0, loc=((x0 + x1) / 2, y, z), material=material, axis='X')
bevel(ob, 0.01, 2)
return ob
def hull2d(pts):
pts = sorted(set(pts))
def cross(o, a, b):
return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0])
lower, upper = [], []
for p in pts:
while len(lower) >= 2 and cross(lower[-2], lower[-1], p) <= 0:
lower.pop()
lower.append(p)
for p in reversed(pts):
while len(upper) >= 2 and cross(upper[-2], upper[-1], p) <= 0:
upper.pop()
upper.append(p)
return lower[:-1] + upper[:-1]
def circ(cx, cz, r, n=24, a0=0.0, a1=2 * math.pi):
return [(cx + r * math.cos(a0 + (a1 - a0) * i / n), cz + r * math.sin(a0 + (a1 - a0) * i / n))
for i in range(n + 1)]
# ================================================================ 1. 기초와 베이스
pad = box("Foundation_ConcretePad", (13.2, 3.6, PAD_H), loc=(-5.3, 0, 0), material=CONCRETE, base=True)
bevel(pad, 0.04, 2)
X_BACK, X_FRONT = -11.4, -1.9
for s in (-1, 1):
ibeam("Base_SkidRail_" + ("L" if s < 0 else "R"), X_BACK, X_FRONT, SKID_H, 0.26, 0.03, 0.03,
y=s * 0.9, z=PAD_H + SKID_H / 2, material=BLUE)
for i, x in enumerate((-11.2, -10.3, -8.3, -6.2, -4.9, -2.1)):
cm = box(f"Base_CrossMember_{i}", (0.22, 1.54, SKID_H - 0.06), loc=(x, 0, PAD_H), material=BLUE, base=True)
bevel(cm, 0.012, 2)
# 감속기 받침대
gbase = box("GearReducer_SubBase", (2.7, 1.1, ZC - 1.15 - SK), loc=(XG - 0.35, 0, SK), material=BLUE, base=True)
bevel(gbase, 0.02, 1)
box("GearReducer_BaseDeck", (3.0, 1.95, 0.05), loc=(XG - 0.35, 0, SK - 0.05), material=BLUE, base=True)
# ================================================================ 2. 샘슨 포스트(A 프레임)
TOP_Z = ZS - 0.42
legs = []
for s in (-1, 1):
side = "L" if s < 0 else "R"
f0, f1 = (XS + 1.25, s * 0.9, SK), (XS + 0.12, s * 0.32, TOP_Z)
r0, r1 = (XS - 2.5, s * 0.9, SK), (XS - 0.12, s * 0.32, TOP_Z)
legs.append(strut(f"SamsonPost_FrontLeg_{side}", f0, f1, 0.24, 0.24, BLUE))
legs.append(strut(f"SamsonPost_RearLeg_{side}", r0, r1, 0.22, 0.22, BLUE))
# 옆면 X 가새
def lerp(a, b, t):
return tuple(a[k] + (b[k] - a[k]) * t for k in range(3))
for t0, t1 in ((0.08, 0.5), (0.5, 0.82)):
strut(f"SamsonPost_Brace_{side}_{t0}", lerp(f0, f1, t0), lerp(r0, r1, t1), 0.08, 0.1, BLUE)
strut(f"SamsonPost_Brace_{side}_{t0}b", lerp(r0, r1, t0), lerp(f0, f1, t1), 0.08, 0.1, BLUE)
# 앞뒤 가로대
for t in (0.33, 0.66):
for nm, a0, a1 in (("Front", (XS + 1.25, 0.9, SK), (XS + 0.12, 0.32, TOP_Z)),
("Rear", (XS - 2.5, 0.9, SK), (XS - 0.12, 0.32, TOP_Z))):
p = [a0[k] + (a1[k] - a0[k]) * t for k in range(3)]
strut(f"SamsonPost_Tie{nm}_{t}", (p[0], -p[1], p[2]), (p[0], p[1], p[2]), 0.1, 0.12, BLUE)
cap = box("SamsonPost_TopPlate", (0.75, 1.0, 0.1), loc=(XS, 0, TOP_Z - 0.04), material=BLUE, base=True)
# 새들 베어링(고정)
box("SaddleBearing_Pedestal", (0.5, 0.86, 0.14), loc=(XS, 0, TOP_Z + 0.06), material=STEEL, base=True)
ycyl("SaddleBearing_Housing", 0.2, -0.42, 0.42, XS, ZS, STEEL)
ycyl("SaddleBearing_Shaft", 0.09, -0.52, 0.52, XS, ZS, GALV, seg=16)
# 사다리(뒤쪽 다리 사이 면)
lad0, lad1 = Vector((XS - 2.5 - 0.15, 0, SK)), Vector((XS - 0.12 - 0.35, 0, TOP_Z - 0.2))
for s in (-1, 1):
strut("Ladder_Rail_" + ("L" if s < 0 else "R"), lad0 + Vector((0, s * 0.22, 0)), lad1 + Vector((0, s * 0.22, 0)),
0.06, 0.03, GALV)
n_rung = int((lad1 - lad0).length / 0.32)
rung0 = None
for i in range(1, n_rung):
p = lad0 + (lad1 - lad0) * (i / n_rung)
if rung0 is None:
rung0 = ycyl("Ladder_Rung", 0.016, -0.22, 0.22, p.x, p.z, GALV, seg=8)
else:
duplicate(rung0, loc=(p.x, 0, p.z))
# ================================================================ 3. 워킹빔 + 말머리 (피벗 기준 로컬 좌표)
beam = empty("WalkingBeam_Pivot", (XS, 0, ZS), (0, -BETA, 0))
parts = []
parts.append(ibeam("WalkingBeam_IBeam", -C - 0.55, A - 2.0, 0.82, 0.42, 0.04, 0.045, z=0.66, material=BLUE))
parts.append(box("WalkingBeam_Saddle", (0.9, 0.5, 0.32), loc=(0, 0, 0.1), material=STEEL, base=False))
# 웹 보강판
st0 = None
for i, x in enumerate([-3.0, -2.2, -1.4, -0.6, 0.6, 1.2]):
for s in (-1, 1):
if st0 is None:
st0 = box("WalkingBeam_Stiffener", (0.03, 0.17, 0.73), loc=(x, s * 0.11, 0.66), material=BLUE)
parts.append(st0)
else:
parts.append(duplicate(st0, loc=(x, s * 0.11, 0.66)))
# 말머리
a0, a1 = math.radians(-42), math.radians(22)
arc = [(A * math.cos(a0 + (a1 - a0) * i / 64), A * math.sin(a0 + (a1 - a0) * i / 64)) for i in range(65)]
head_pts = arc + [(2.55, 1.22), (1.55, 1.07), (1.55, 0.25), (1.95, -0.55), (2.35, -1.55), (2.62, -2.42)]
head = extrude_profile("HorseHead", head_pts, 0.66, material=YELLOW)
bevel(head, 0.035, 3)
parts.append(head)
# 말머리 앞면 와이어 홈 띠
band_out = [((A + 0.03) * math.cos(a0 + (a1 - a0) * i / 64), (A + 0.03) * math.sin(a0 + (a1 - a0) * i / 64)) for i in range(65)]
band_in = [((A - 0.12) * math.cos(a0 + (a1 - a0) * i / 64), (A - 0.12) * math.sin(a0 + (a1 - a0) * i / 64)) for i in range(65)]
parts.append(extrude_profile("HorseHead_FaceBand", band_out + band_in[::-1], 0.5, material=STEEL))
# 이퀄라이저 베어링과 가로보
parts.append(box("Equalizer_BearingBlock", (0.5, 0.5, 0.3), loc=(-C, 0, 0.12), material=STEEL))
parts.append(ycyl("Equalizer_Beam", 0.13, -YP - 0.06, YP + 0.06, -C, 0.0, STEEL, seg=24))
for s in (-1, 1):
parts.append(box("Equalizer_Yoke", (0.42, 0.14, 0.34), loc=(-C, s * (YP - 0.13), 0.02), material=BLUE))
for p in parts:
p.parent = beam
def beam_world(lx, lz):
c, s = math.cos(BETA), math.sin(BETA)
return (XS + lx * c - lz * s, ZS + lx * s + lz * c)
# ================================================================ 4. 감속기
hub = circ(XG, ZC, 0.9, 40)
inp_x, inp_z = XG - 1.15, ZC - 0.5
gear_pts = hull2d(hub + circ(inp_x, inp_z, 0.5, 24) + [(XG - 1.65, ZC - 1.15), (XG + 0.9, ZC - 1.15)])
gb = extrude_profile("GearReducer_Housing", gear_pts, 1.0, material=BLUE)
bevel(gb, 0.04, 2)
# 분할면 플랜지(축 높이를 지나는 수평 띠)
fl = box("GearReducer_SplitFlange", (2.9, 1.08, 0.07), loc=(XG - 0.4, 0, ZC), material=BLUE)
box("GearReducer_InspectionCover", (0.6, 0.6, 0.06), loc=(XG - 0.25, 0, ZC + 0.88), material=STEEL)
ycyl("GearReducer_LowSpeedShaft", 0.16, -0.95, 0.95, XG, ZC, GALV, seg=24)
ycyl("GearReducer_InputShaft", 0.07, -1.45, 0.75, inp_x, inp_z, GALV, seg=16)
for s in (-1, 1):
ycyl("GearReducer_BearingCap", 0.3, s * 0.5 - 0.06, s * 0.5 + 0.06, XG, ZC, STEEL)
ycyl("GearReducer_InputBearingCap", 0.17, s * 0.5 - 0.05, s * 0.5 + 0.05, inp_x, inp_z, STEEL)
# 브레이크 드럼(반대편)
ycyl("Brake_Drum", 0.3, 0.62, 0.78, inp_x, inp_z, STEEL)
strut("Brake_Lever", (XG - 1.3, 1.05, SK + 0.05), (XG - 1.75, 1.05, SK + 1.5), 0.06, 0.06, RED)
# ================================================================ 5. 크랭크와 평형추
crank = empty("Crank_Rotor", (XG, 0, ZC), (0, -PHI, 0))
cparts = []
arm_pts = circ(0, 0, 0.36, 16, math.pi / 2, 3 * math.pi / 2) + [(RP + 0.2, -0.2), (RP + 0.3, 0.0), (RP + 0.2, 0.2)]
cw_pts = [(0.45, -0.55), (0.45, 0.55)] + [(1.62 * math.cos(a), 1.62 * math.sin(a))
for a in [math.radians(30 - 60 * i / 20) for i in range(21)]]
for s in (-1, 1):
side = "L" if s < 0 else "R"
arm = extrude_profile(f"Crank_Arm_{side}", arm_pts, 0.16, loc=(0, s * 0.74, 0), material=BLUE)
bevel(arm, 0.02, 3)
cw = extrude_profile(f"Counterweight_{side}", hull2d(cw_pts), 0.44, loc=(0, s * 0.79, 0), material=YELLOW)
bevel(cw, 0.04, 3)
cparts += [arm, cw]
cparts.append(ycyl(f"Crank_WristPin_{side}", 0.075, s * 0.66 if s > 0 else -1.24,
1.24 if s > 0 else -0.66, RP, 0.0, GALV, seg=16))
cparts.append(ycyl(f"Crank_Hub_{side}", 0.3, s * 0.74 - 0.12, s * 0.74 + 0.12, 0, 0, STEEL))
for p in cparts:
p.parent = crank
# ================================================================ 6. 피트먼 암
kx, kz = XG + RP * math.cos(PHI), ZC + RP * math.sin(PHI)
ex, ez = beam_world(-C, 0.0)
for s in (-1, 1):
side = "L" if s < 0 else "R"
d = Vector((ex - kx, 0, ez - kz)).normalized()
p0 = Vector((kx, s * YP, kz)) + d * 0.18
p1 = Vector((ex, s * YP, ez)) - d * 0.18
strut(f"Pitman_Arm_{side}", p0, p1, 0.12, 0.2, BLUE)
box(f"Pitman_WristBearing_{side}", (0.34, 0.16, 0.34), loc=(kx, s * YP, kz), material=STEEL)
box(f"Pitman_TopBearing_{side}", (0.34, 0.16, 0.34), loc=(ex, s * YP, ez), material=STEEL)
# ================================================================ 7. 모터와 V벨트 구동부
MX, MZ = XG - 3.0, SK + 0.62
mbase = box("Motor_SlideBase", (1.1, 2.06, MZ - 0.38 - SK), loc=(MX, -0.07, SK), material=BLUE, base=True)
ycyl("Motor_Frame", 0.38, -0.45, 0.55, MX, MZ, MOTOR, seg=40)
ycyl("Motor_FanCowl", 0.36, 0.55, 0.82, MX, MZ, MOTOR, seg=40)
ycyl("Motor_EndBell", 0.3, -0.58, -0.45, MX, MZ, MOTOR, seg=32)
fin0 = None
for i in range(16):
a = 2 * math.pi * i / 16
if math.sin(a) < -0.5:
continue
loc = (MX + 0.4 * math.cos(a), 0.05, MZ + 0.4 * math.sin(a))
if fin0 is None:
fin0 = box("Motor_CoolingFin", (0.05, 0.92, 0.035), loc=loc, rot=(0, -a, 0), material=MOTOR)
else:
duplicate(fin0, loc=loc, rot=(0, -a, 0))
box("Motor_TerminalBox", (0.26, 0.3, 0.2), loc=(MX, 0.05, MZ + 0.36), material=MOTOR, base=True)
for s in (-1, 1):
box("Motor_Foot", (0.16, 0.9, 0.38), loc=(MX + s * 0.26, 0.05, MZ - 0.38), material=MOTOR, base=True)
ycyl("Motor_Shaft", 0.05, -1.45, -0.58, MX, MZ, GALV, seg=12)
# 시브와 벨트
ycyl("Sheave_Gear", 0.62, -1.38, -1.2, inp_x, inp_z, STEEL, seg=48)
ycyl("Sheave_Motor", 0.2, -1.38, -1.2, MX, MZ, STEEL, seg=24)
# 벨트 가드(두 원의 볼록 껍질 형태 판)
guard_pts = hull2d(circ(inp_x, inp_z, 0.78, 32) + circ(MX, MZ, 0.36, 20))
guard = extrude_profile("BeltGuard", guard_pts, 0.34, loc=(0, -1.36, 0), material=MOTOR)
bevel(guard, 0.03, 2)
strut("BeltGuard_Bracket", (MX + 0.4, -1.36, SK), (MX + 0.4, -1.36, MZ - 0.2), 0.08, 0.08, BLUE)
box("BeltGuard_BracketFoot", (0.3, 0.6, 0.06), loc=(MX + 0.4, -1.1, SK), material=BLUE, base=True)
# ================================================================ 8. 브라이들·폴리시드 로드·유정 헤드
XW = XS + A # = 0, 유정 중심
CB_Z = 2.75
for s in (-1, 1):
pts = []
top = a1 + BETA
for i in range(13):
a = top - top * i / 12
pts.append((XS + (A + 0.045) * math.cos(a), s * 0.17, ZS + (A + 0.045) * math.sin(a)))
pts.append((XW + 0.045, s * 0.17, CB_Z + 0.06))
curve_tube("Bridle_WireRope_" + ("L" if s < 0 else "R"), pts, radius=0.022, material=ROPE, resolution=8, bevel_res=3)
box("Bridle_CarrierBar", (0.14, 0.52, 0.12), loc=(XW, 0, CB_Z), material=STEEL)
box("PolishedRod_Clamp", (0.16, 0.16, 0.14), loc=(XW, 0, CB_Z + 0.06), material=STEEL, base=True)
WH = PAD_H
rod("PolishedRod", (XW, 0, WH + 1.45), (XW, 0, CB_Z + 0.5), 0.022, CHROME)
lathe("Wellhead_CasingHead", [(0.0, WH), (0.32, WH), (0.32, WH + 0.08), (0.18, WH + 0.1), (0.18, WH + 0.42),
(0.3, WH + 0.44), (0.3, WH + 0.52), (0.12, WH + 0.54), (0.12, WH + 0.9),
(0.0, WH + 0.9)], material=WELL, seg=48)
lathe("Wellhead_StuffingBox", [(0.0, WH + 0.9), (0.16, WH + 0.9), (0.16, WH + 0.98), (0.1, WH + 1.0),
(0.1, WH + 1.28), (0.13, WH + 1.3), (0.13, WH + 1.38), (0.04, WH + 1.42),
(0.0, WH + 1.42)], material=GALV, seg=40)
# 플로 티와 배관
ycyl("Wellhead_FlowTee", 0.075, 0.0, 0.75, XW, WH + 0.72, WELL, seg=20)
ycyl("Wellhead_ValveBody", 0.13, 0.32, 0.5, XW, WH + 0.72, WELL, seg=20)
rod("Wellhead_ValveStem", (XW, 0.41, WH + 0.85), (XW, 0.41, WH + 1.02), 0.02, GALV, seg=8)
torus("Wellhead_Handwheel", R=0.14, r=0.018, loc=(XW, 0.41, WH + 1.02), material=RED, seg=24, rseg=8)
rod("Flowline_Riser", (XW, 0.75, WH + 0.72), (XW, 0.75, WH + 0.05), 0.075, WELL, seg=20)
sphere("Flowline_Elbow", 0.075, loc=(XW, 0.75, WH + 0.72), material=WELL, seg=16, rings=8)
rod("Flowline_Ground", (XW, 0.75, WH + 0.08), (XW - 1.6, 0.75, WH + 0.08), 0.075, WELL, seg=20)
sphere("Flowline_Elbow2", 0.075, loc=(XW, 0.75, WH + 0.08), material=WELL, seg=16, rings=8)
box("Wellhead_PipeSupport", (0.2, 0.25, 0.04), loc=(XW - 1.0, 0.75, WH), material=STEEL, base=True)
# ================================================================ 9. 3차 디테일
# 말머리 옆면 테두리 보강판(측판 가장자리를 따라 도드라진 띠)
rim_out = [((A - 0.02) * math.cos(a0 + (a1 - a0) * i / 64), (A - 0.02) * math.sin(a0 + (a1 - a0) * i / 64)) for i in range(65)]
rim_in = [((A - 0.2) * math.cos(a1 - (a1 - a0) * i / 64), (A - 0.2) * math.sin(a1 - (a1 - a0) * i / 64)) for i in range(65)]
for s in (-1, 1):
rim = extrude_profile("HorseHead_SideRim_" + ("L" if s < 0 else "R"), rim_out + rim_in, 0.03,
loc=(0, s * 0.34, 0), material=YELLOW)
rim.parent = beam
# 말머리를 빔에 고정하는 핀 러그
for s in (-1, 1):
ycyl("HorseHead_LatchPin", 0.06, -0.4, 0.4, 1.75, 0.66, GALV, seg=12, parent=beam)
# 볼트 — 하나의 메시를 여러 곳에 연결 복제
bolt = cylinder("Bolt", r=0.035, h=0.05, rot=(math.pi / 2, 0, 0), material=GALV, seg=6, base=False)
bolt.hide_render = True
bolt.location = (0, 0, -50)
def bolts_y(cx, cz, rad, n, y, parent=None, a0=0.0):
for i in range(n):
a = a0 + 2 * math.pi * i / n
b = duplicate(bolt, loc=(cx + rad * math.cos(a), y, cz + rad * math.sin(a)), name="Bolt")
b.hide_render = False
if parent:
b.parent = parent
for s in (-1, 1):
bolts_y(XG, ZC, 0.24, 8, s * 0.575, None) # 저속축 베어링 캡
bolts_y(0, 0, 0.22, 6, s * 0.875, crank) # 크랭크 허브
bolts_y(XS, ZS, 0.15, 6, s * 0.435, None) # 새들 베어링
for r in (0.8, 1.1): # 평형추 고정 볼트
for a in (-0.25, 0.25):
b = duplicate(bolt, loc=(r * math.cos(a), s * 1.0, r * math.sin(a)), name="Bolt")
b.hide_render = False
b.parent = crank
# 감속기 분할면 볼트
for i in range(9):
x = XG - 1.65 + i * 0.32
for s in (-1, 1):
b = duplicate(bolt, loc=(x, s * 0.55, ZC + 0.07), name="Bolt")
b.hide_render = False
# 감속기 케이스 리브
for x in (XG - 0.55, XG + 0.35):
for s in (-1, 1):
box("GearReducer_Rib", (0.06, 0.06, 0.8), loc=(x, s * 0.52, ZC - 0.6), material=BLUE)
# 베이스 앵커 볼트
anchor = None
for x in (-11.0, -10.3, -8.3, -6.2, -4.9, -2.3):
for s in (-1, 1):
for dy in (-0.09, 0.09):
loc = (x, s * 0.9 + dy, PAD_H + 0.03 + 0.04)
if anchor is None:
anchor = cylinder("Base_AnchorBolt", r=0.025, h=0.06, loc=loc, material=GALV, seg=6)
else:
duplicate(anchor, loc=loc)
# 샘슨 포스트 다리 밑판(거싯)
for x in (XS + 1.25, XS - 2.5):
for s in (-1, 1):
box("SamsonPost_FootPlate", (0.5, 0.4, 0.04), loc=(x, s * 0.9, SK), material=BLUE, base=True)
# 펌프 오프 컨트롤러(전기 제어반)와 전선관
PX, PY = -10.6, -2.3
box("Controller_Post", (0.1, 0.1, 1.5), loc=(PX, PY, PAD_H), material=GALV, base=True)
box("Controller_Post2", (0.1, 0.1, 1.5), loc=(PX + 0.7, PY, PAD_H), material=GALV, base=True)
box("Controller_Rail", (0.9, 0.08, 0.08), loc=(PX + 0.35, PY, PAD_H + 1.42), material=GALV)
cab = box("Controller_Cabinet", (0.62, 0.3, 0.8), loc=(PX + 0.35, PY - 0.2, PAD_H + 0.62), material=MOTOR, base=True)
bevel(cab, 0.015, 1)
box("Controller_Door", (0.54, 0.02, 0.7), loc=(PX + 0.35, PY - 0.36, PAD_H + 0.67), material=MOTOR, base=True)
box("Controller_Handle", (0.03, 0.03, 0.12), loc=(PX + 0.56, PY - 0.385, PAD_H + 0.95), material=GALV)
cylinder("Controller_Lamp", r=0.03, h=0.04, loc=(PX + 0.2, PY - 0.37, PAD_H + 1.2), rot=(math.pi / 2, 0, 0),
material=RED, seg=12, base=False)
curve_tube("Controller_Conduit", [(PX + 0.35, PY - 0.2, PAD_H + 0.62), (PX + 0.35, PY - 0.2, PAD_H + 0.3),
(PX + 0.35, PY - 0.2, PAD_H + 0.05), (MX - 0.1, -1.05, PAD_H + 0.05),
(MX - 0.1, -1.05, MZ + 0.1), (MX - 0.05, -0.2, MZ + 0.46)],
radius=0.025, material=GALV)
# 컨트롤러 받침 기초
box("Controller_Footing", (1.1, 0.5, PAD_H), loc=(PX + 0.35, PY - 0.05, 0), material=CONCRETE, base=True)
# 말머리 측판 리벳 열
for s in (-1, 1):
for i in range(28):
a = a0 + (a1 - a0) * (i + 0.5) / 28
b = duplicate(bolt, loc=((A - 0.11) * math.cos(a), s * 0.365, (A - 0.11) * math.sin(a)), name="Rivet")
b.hide_render = False
b.parent = beam
# 빔 윗 플랜지 볼트(보강판 위치)
for x in [-3.0, -2.2, -1.4, -0.6, 0.6, 1.2]:
for s in (-1, 1):
b = duplicate(bolt, loc=(x, s * 0.15, 1.07), rot=(0, 0, 0), name="Bolt")
b.hide_render = False
b.parent = beam
# 다리 밑판 볼트
for x in (XS + 1.25, XS - 2.5):
for s in (-1, 1):
for dx in (-0.18, 0.18):
for dy in (-0.14, 0.14):
b = duplicate(bolt, loc=(x + dx, s * 0.9 + dy, SK + 0.06), rot=(0, 0, 0), name="Bolt")
b.hide_render = False
# 크랭크 회전 구역 안전 난간(양옆 파이프 펜스)
post0 = None
FX = [-10.9, -9.7, -8.5, -7.3, -6.1]
for sy in (-1, 1):
for x in FX:
loc = (x, sy * 1.66, PAD_H)
if post0 is None:
post0 = cylinder("SafetyRail_Post", r=0.03, h=1.12, loc=loc, material=YELLOW, seg=16)
else:
duplicate(post0, loc=loc)
for zr in (0.55, 1.1):
curve_tube("SafetyRail_Pipe", [(FX[0], sy * 1.66, PAD_H + zr), (FX[-1], sy * 1.66, PAD_H + zr)],
radius=0.026, material=YELLOW, resolution=2, bevel_res=4)
for x in FX:
box("SafetyRail_BasePlate", (0.14, 0.14, 0.02), loc=(x, sy * 1.66, PAD_H), material=YELLOW, base=True)
bpy.data.objects.remove(bolt, do_unlink=True)

다른 모형