본문으로 건너뛰기
3D 아틀리에
드레드넛 통기타 3D 모형

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

이 모형에 대하여

드레드넛은 스틸 줄을 쓰는 어쿠스틱 기타에서 가장 널리 쓰이는 몸통형이다. 미국의 C. F. 마틴 사가 1916년 처음 만들었고, 이름은 당시 영국 해군의 대형 전함 드레드넛에서 따왔다. 당시 기타보다 몸통이 크고 깊어 저음이 풍부하고 음량이 커서, 컨트리·포크·블루그래스 연주자들 사이에서 표준처럼 자리 잡았다.

허리가 비교적 얕게 들어가고 위 볼의 어깨가 각진 것이 드레드넛을 알아보게 하는 특징이다. 가문비나무 앞판에는 둥근 사운드홀과 상감 로제트가 있고, 그 아래로 물방울 모양 픽가드와 브리지가 놓인다. 넥은 14번 프렛에서 몸통과 만나며, 뒤로 꺾인 헤드에 줄감개가 양쪽에 셋씩 달린다.

이 모델은 로즈우드 옆·뒤판과 상아색 바인딩, 흑단 지판을 갖춘 전형적인 드레드넛을 접이식 스탠드에 기대 세운 모습으로 표현했다. 줄 길이 645mm를 기준으로 프렛과 브리지 위치를 계산해, 실제 악기와 같은 비례로 지판과 몸통이 이어진다.

만든 방법

몸통 윤곽은 오른쪽 절반의 기준점 17개를 캐트멀-롬 곡선으로 잇고 좌우 대칭으로 닫은 뒤 호 길이로 192점 재표본했다. 이 윤곽을 안쪽 법선 방향으로 조금씩 들여 8층으로 쌓아 둥근 모서리를 만들고, 층 사이 면마다 앞판·바인딩·옆판 재질 번호를 나눠 한 메시에 담았다. 사운드홀은 EXACT 불리언으로 뚫어 속면에 어두운 재질을 옮겼고, 로제트와 퍼플링은 앞판을 살짝 관통하는 얇은 고리로 넣어 면 겹침을 피했다. 넥은 반타원 단면 61개를 이어 힐과 헤드 쪽 전이까지 한 번에 만들었고, 프렛 20개는 하나의 메시를 폭만 다르게 늘려 공유했다. 줄은 브리지 핀-새들-너트-줄감개 기둥을 잇는 관이며, 나뭇결·줄의 감긴 질감·상표 로고는 생략했다.

build.py

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

# 드레드넛 통기타(스틸 스트링 어쿠스틱 기타)와 접이식 기타 스탠드.
# 몸통 윤곽은 손으로 찍은 기준점을 캐트멀-롬 곡선으로 이어 만들고, 그 윤곽을 층층이 쌓아(로프트)
# 앞판·바인딩·옆판·뒤판을 하나의 메시에 면별 재질로 나눴다. 사운드홀은 불리언으로 뚫었다.
# 넥은 반타원 단면을 길이 방향으로 이어 만들었고, 프렛 위치는 평균율 공식 S·(1-2^(-n/12))로 계산했다.
# 헤드는 14° 뒤로 꺾인 판에 3+3 줄감개를 달았고, 여섯 줄은 브리지 핀-새들-너트-줄감개 기둥을 잇는 관이다.
# 기타는 로컬 좌표(앞판 +Z, 넥 +Y)에서 만든 뒤 빈 오브젝트에 묶어 75° 세워 스탠드에 기대 놓았다.
from atelier import *
SHOT = {"azimuth": -62, "elevation": 10, "lens": 60, "light": "studio", "background": "#25323b"}
# ---------------------------------------------------------------- 재질
M_TOP = mat('Spruce_Top', '#d8b27a', 0.38, coat=0.6)
M_BACK = mat('Rosewood_BackSides', '#4b2617', 0.32, coat=0.6)
M_NECK = mat('Mahogany_Neck', '#6e3a22', 0.4, coat=0.4)
M_EBONY = mat('Ebony_Fretboard', '#251a15', 0.45)
M_BIND = mat('Ivoroid_Binding', '#e6dbbf', 0.35)
M_HOLE = mat('Body_Interior', '#2e1c11', 0.8)
M_GUARD = mat('Tortoise_Pickguard', '#4a1a0c', 0.42)
M_FRET = mat('Nickel_Fretwire', '#cfc9bb', 0.22, 1.0)
M_CHROME = mat('Chrome_Tuners', '#d6d8da', 0.12, 1.0)
M_BRONZE = mat('Bronze_Strings', '#c09a5c', 0.3, 1.0)
M_STEEL = mat('Steel_Strings', '#c6c7c8', 0.2, 1.0)
M_PEARL = mat('Pearl_Inlay', '#ece6d8', 0.2)
M_STAND = mat('Stand_PowderCoat', '#2c2d30', 0.45, 0.6)
M_RUBBER = mat('Stand_Rubber', '#3a2f2b', 0.85)
# ---------------------------------------------------------------- 치수(미터)
BODY_L = 0.505 # 몸통 길이
SCALE = 0.645 # 줄 길이(너트~새들)
fret_d = lambda n: SCALE * (1 - 2 ** (-n / 12.0))
Y_NUT = BODY_L + fret_d(14) # 14번 프렛이 몸통 끝과 만난다
Y_SADDLE = Y_NUT - SCALE
HOLE_C = (0.0, 0.362)
HOLE_R = 0.0505
FB_TOP = 0.006 # 지판 윗면 높이
HEAD_ANG = math.radians(14)
def depth_at(y):
"""몸통 두께: 꼬리 쪽 12.2cm → 넥 쪽 10cm."""
return 0.122 - 0.022 * min(max(y / BODY_L, 0), 1)
# ---------------------------------------------------------------- 공용 도구
def catmull(pts, n_per=24):
"""닫힌 캐트멀-롬 곡선 샘플링."""
out = []
N = len(pts)
나머지 356줄 펼치기
for i in range(N):
p0, p1, p2, p3 = (Vector(pts[(i + k) % N]) for k in (-1, 0, 1, 2))
for s in range(n_per):
t = s / n_per
t2, t3 = t * t, t * t * t
out.append(0.5 * ((2 * p1) + (-p0 + p2) * t + (2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 + (-p0 + 3 * p1 - 3 * p2 + p3) * t3))
return out
def resample(pts, n):
"""닫힌 다각형을 호 길이로 균등 재표본."""
seg = [(pts[i] - pts[(i + 1) % len(pts)]).length for i in range(len(pts))]
total = sum(seg)
out, acc, i = [], 0.0, 0
for k in range(n):
target = total * k / n
while acc + seg[i] < target:
acc += seg[i]
i += 1
t = (target - acc) / seg[i]
out.append(pts[i].lerp(pts[(i + 1) % len(pts)], t))
return out
def loft(name, rings, mats, ring_mats, cap_start=None, cap_end=None, closed=True, smooth_all=True):
"""같은 점 개수의 고리들을 차례로 잇는다. ring_mats[i] = i번째와 i+1번째 고리 사이 면의 재질 번호."""
bm = bmesh.new()
vr = [[bm.verts.new(p) for p in r] for r in rings]
n = len(rings[0])
for i in range(len(rings) - 1):
a, b = vr[i], vr[i + 1]
for j in range(n if closed else n - 1):
j2 = (j + 1) % n
f = bm.faces.new((a[j], a[j2], b[j2], b[j]))
f.material_index = ring_mats[i]
f.smooth = smooth_all
if cap_start is not None:
f = bm.faces.new(list(reversed(vr[0])))
f.material_index = cap_start
if cap_end is not None:
f = bm.faces.new(vr[-1])
f.material_index = cap_end
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)
for m in mats:
me.materials.append(m)
return ob
def annulus(name, r1, r2, z0, z1, material, seg=96):
return lathe(name, [(r1, z0), (r2, z0), (r2, z1), (r1, z1), (r1, z0)], material=material, seg=seg, shade_smooth=False)
def tube(name, points, radius, material, bevel_res=3):
"""curve_tube 를 만든 뒤 바로 메시로 바꾼다(경계상자·내보내기 안정)."""
cu = curve_tube(name, points, radius=radius, material=material, resolution=1, bevel_res=bevel_res)
dg = bpy.context.evaluated_depsgraph_get()
me = bpy.data.meshes.new_from_object(cu.evaluated_get(dg))
bpy.data.objects.remove(cu, do_unlink=True)
ob = bpy.data.objects.new(name, me)
link(ob)
return smooth(ob)
GUITAR_PARTS = []
def G(ob):
GUITAR_PARTS.append(ob)
return ob
# ================================================================ 몸통
# 오른쪽 절반 기준점(x=반폭, y=꼬리에서의 거리). 아래 볼 40cm, 허리 27cm, 위 볼 29cm.
half = [(0.0, 0.0), (0.075, 0.004), (0.135, 0.022), (0.176, 0.052), (0.197, 0.098), (0.199, 0.145),
(0.188, 0.195), (0.165, 0.243), (0.142, 0.285), (0.137, 0.312), (0.141, 0.345), (0.146, 0.385),
(0.146, 0.425), (0.139, 0.462), (0.118, 0.488), (0.075, 0.503), (0.0, BODY_L)]
key = half + [(-x, y) for x, y in reversed(half[1:-1])]
outline = resample(catmull([Vector((x, y, 0)) for x, y in key], 20), 192)
N = len(outline)
norms = []
for i in range(N):
t = (outline[(i + 1) % N] - outline[i - 1]).normalized()
norms.append(Vector((-t.y, t.x, 0))) # 반시계 윤곽의 안쪽 법선
def ring_at(zfn, inset):
return [Vector((p.x + nrm.x * inset, p.y + nrm.y * inset, zfn(p.y))) for p, nrm in zip(outline, norms)]
R = 0.0024
layers = [
(lambda y: 0.0, R), (lambda y: -0.0007, 0.0007), (lambda y: -R, 0.0),
(lambda y: -0.0065, 0.0),
(lambda y: -depth_at(y) + 0.0065, 0.0),
(lambda y: -depth_at(y) + R, 0.0), (lambda y: -depth_at(y) + 0.0007, 0.0007), (lambda y: -depth_at(y), R),
]
rings = [ring_at(z, ins) for z, ins in layers]
# 재질 번호: 0 앞판, 1 바인딩, 2 옆·뒤판, 3 속(사운드홀)
body = loft('Guitar_Body', rings, [M_TOP, M_BIND, M_BACK, M_HOLE], [1, 1, 1, 2, 1, 1, 1], cap_start=0, cap_end=2)
for p in body.data.polygons:
if p.material_index in (0, 2) and abs(p.normal.z) > 0.9:
p.use_smooth = False
hole_cut = cylinder('SoundHole_Cutter', HOLE_R, 0.105, loc=(HOLE_C[0], HOLE_C[1], -0.095), material=M_HOLE, seg=64)
boolean(body, hole_cut)
body.modifiers[-1].material_mode = 'TRANSFER'
apply_all(body)
G(body)
# 로제트(사운드홀 둘레 상감 고리)
G(annulus('Rosette_Band', 0.0565, 0.0635, -0.0004, 0.0002, M_BACK))
G(annulus('Rosette_Ring_Inner', 0.0555, 0.0575, -0.0004, 0.0004, M_BIND))
G(annulus('Rosette_Ring_Outer', 0.0625, 0.0645, -0.0004, 0.0004, M_BIND))
G(annulus('Rosette_Ring_Outer2', 0.0660, 0.0668, -0.0004, 0.0003, M_EBONY))
G(annulus('SoundHole_Edge', HOLE_R - 0.0001, HOLE_R + 0.0012, -0.0035, 0.0001, M_TOP))
for ob in GUITAR_PARTS[-5:]:
ob.location = (HOLE_C[0], HOLE_C[1], 0)
# 앞판 가장자리 퍼플링(검은 줄)
pur = loft('Top_Purfling', [ring_at(lambda y: -0.0004, 0.0042), ring_at(lambda y: 0.00025, 0.0042),
ring_at(lambda y: 0.00025, 0.0058), ring_at(lambda y: -0.0004, 0.0058)],
[M_EBONY], [0, 0, 0], smooth_all=False)
G(pur)
# 뒤판 가운데 띠(백 스트립)
_sl = math.atan(0.022 / BODY_L)
G(box('Back_Center_Strip', (0.0055, 0.484, 0.0012), loc=(0, 0.2535, -depth_at(0.2535)), rot=(_sl, 0, 0), material=M_BIND))
# 픽가드(물방울형, 사운드홀 오른쪽 아래를 감싼다)
pg = []
A0, A1 = math.radians(-118), math.radians(28)
K = 40
for k in range(K + 1):
a = A0 + (A1 - A0) * k / K
pg.append((HOLE_C[0] + 0.0688 * math.cos(a), HOLE_C[1] + 0.0688 * math.sin(a)))
for k in range(K, -1, -1):
u = k / K
a = A0 + (A1 - A0) * u
w = 0.064 * math.sin(math.pi * (u ** 0.85)) ** 0.55 * (1 - 0.35 * u)
r = 0.0688 + w
pg.append((HOLE_C[0] + r * math.cos(a), HOLE_C[1] + r * math.sin(a)))
G(prism('Pickguard', pg[:-1], 0.0007, loc=(0, 0, -0.0003), material=M_GUARD))
# 브리지(배불뚝이형)와 새들, 브리지 핀
bp = []
BL, BW = 0.076, 0.0155
for k in range(25):
t = -1 + 2 * k / 24
bp.append((BL * t, -BW - 0.004 * math.cos(t * math.pi / 2) ** 2))
for k in range(25):
t = 1 - 2 * k / 24
bp.append((BL * t * 0.985, BW))
bridge = prism('Bridge', bp, 0.0095, loc=(0, Y_SADDLE - 0.006, -0.0005), material=M_EBONY)
bevel(bridge, 0.0025, 3, 40)
G(bridge)
saddle = box('Saddle', (0.074, 0.0028, 0.0055), loc=(0, Y_SADDLE, 0.0074), rot=(0, 0, math.radians(1.5)), material=M_BIND, base=True)
bevel(saddle, 0.0009, 2, 40)
G(saddle)
Y_PIN = Y_SADDLE - 0.0135
pin = join([cylinder('Pin_shaft', 0.0026, 0.004, loc=(0, 0, 0.0075), material=M_BIND, seg=16),
sphere('Pin_head', 0.0034, loc=(0, 0, 0.0108), material=M_BIND, seg=16, rings=8, scale=(1, 1, 0.55))], 'Bridge_Pin_1')
G(pin)
STR_X_SADDLE = [-0.0262 + 0.0524 * i / 5 for i in range(6)]
STR_X_NUT = [-0.0178 + 0.0356 * i / 5 for i in range(6)]
for i, x in enumerate(STR_X_SADDLE):
if i == 0:
pin.location = (x, Y_PIN, 0.0075)
else:
G(duplicate(pin, loc=(x, Y_PIN, 0.0075), name=f'Bridge_Pin_{i + 1}'))
# 꼬리 스트랩 버튼
G(join([cylinder('EndPin_base', 0.0055, 0.004, rot=(math.pi / 2, 0, 0), loc=(0, 0.0015, -0.06), material=M_BIND, seg=20),
cylinder('EndPin_knob', 0.0045, 0.008, r2=0.0035, rot=(math.pi / 2, 0, 0), loc=(0, -0.002, -0.06), material=M_BIND, seg=20)],
'End_Pin'))
# ================================================================ 넥
FB_END = 0.4175
def fb_half(y):
return 0.5 * (0.0435 + (Y_NUT - y) * (0.0125 / fret_d(14)))
def neck_depth(y):
base = 0.0212 + 0.0028 * min(max((Y_NUT - y) / (Y_NUT - 0.57), 0), 1)
t = min(max((0.562 - y) / (0.562 - 0.506), 0), 1)
heel = t * t * (3 - 2 * t)
return base + (depth_at(0.5) - 0.002 - base) * heel
nrings = []
ys = [0.497 + (Y_NUT + 0.028 - 0.497) * k / 60 for k in range(61)]
for y in ys:
hw = fb_half(min(y, Y_NUT))
d = neck_depth(min(y, Y_NUT))
ztop = 0.0
if y > Y_NUT:
u = (y - Y_NUT) / 0.028
ztop = -(y - Y_NUT) * math.tan(HEAD_ANG) - 0.0016 * min(u * 4, 1)
d = d + (0.0152 - d) * u
if y < 0.575:
hw *= 0.93 + 0.07 * (y - 0.497) / 0.078
ring = []
for j in range(19):
a = math.pi * j / 18
s = math.sin(a)
ring.append(Vector((hw * math.cos(a), y, ztop - d * s ** 0.85)))
nrings.append(ring)
neck = loft('Neck', nrings, [M_NECK], [0] * 60, cap_start=0, cap_end=0, closed=True)
G(neck)
# 지판
fbp = [(-fb_half(FB_END), FB_END), (fb_half(FB_END), FB_END), (fb_half(Y_NUT), Y_NUT), (-fb_half(Y_NUT), Y_NUT)]
G(prism('Fretboard', fbp, FB_TOP + 0.0005, loc=(0, 0, -0.0005), material=M_EBONY))
# 프렛 20개(같은 메시를 폭만 늘려 공유)
fret = cylinder('Fret_01', 0.0011, 1.0, rot=(0, math.pi / 2, 0), loc=(-0.5, 0, 0), material=M_FRET, seg=10, base=True)
G(fret)
for n in range(1, 21):
y = Y_NUT - fret_d(n)
w = 2 * fb_half(y) - 0.0004
ob = fret if n == 1 else G(duplicate(fret, name=f'Fret_{n:02d}'))
ob.location = (-w / 2, y, FB_TOP - 0.0002)
ob.scale = (1, 1, w)
# 포지션 마크(자개 점)
dot = cylinder('Inlay_Dot', 0.0032, 0.0008, loc=(0, 0, FB_TOP - 0.0007), material=M_PEARL, seg=20)
G(dot)
first = True
for n in (3, 5, 7, 9, 12, 15, 17):
y = Y_NUT - 0.5 * (fret_d(n) + fret_d(n - 1))
for x in ((-0.0075, 0.0075) if n == 12 else (0.0,)):
if first:
dot.location = (x, y, FB_TOP - 0.0007)
first = False
else:
G(duplicate(dot, loc=(x, y, FB_TOP - 0.0007), name=f'Inlay_Dot_{n}'))
# 너트
nut = box('Nut', (2 * fb_half(Y_NUT), 0.005, 0.0088), loc=(0, Y_NUT + 0.0025, -0.0005), material=M_BIND, base=True)
bevel(nut, 0.0008, 2, 40)
G(nut)
# ================================================================ 헤드
HEAD_L = 0.19
head_m = Matrix.Translation((0, Y_NUT, 0)) @ Matrix.Rotation(-HEAD_ANG, 4, 'X')
hp = [(-0.0375, 0.0), (0.0375, 0.0), (0.0435, HEAD_L - 0.012), (0.04, HEAD_L), (-0.04, HEAD_L), (-0.0435, HEAD_L - 0.012)]
bmh = bmesh.new()
bot = [bmh.verts.new((x, y, -0.0155)) for x, y in hp]
topv = [bmh.verts.new((x, y, 0.0)) for x, y in hp]
bmh.faces.new(list(reversed(bot))).material_index = 0
bmh.faces.new(topv).material_index = 1
for i in range(len(hp)):
bmh.faces.new((bot[i], bot[(i + 1) % len(hp)], topv[(i + 1) % len(hp)], topv[i])).material_index = 0
bmesh.ops.recalc_face_normals(bmh, faces=bmh.faces)
me = bpy.data.meshes.new('Headstock')
bmh.to_mesh(me)
bmh.free()
me.materials.append(M_NECK)
me.materials.append(M_EBONY)
headstock = bpy.data.objects.new('Headstock', me)
link(headstock)
headstock.matrix_world = head_m
bevel(headstock, 0.0018, 2, 40)
G(headstock)
# 줄감개 3+3 (기둥은 앞면, 기어 상자와 손잡이는 뒷면 바깥쪽)
POST_Y = [0.045, 0.087, 0.129]
POST_X = 0.0255
tuner_parts = [
cylinder('Tuner_bushing', 0.0048, 0.0022, loc=(0, 0, -0.0004), material=M_CHROME, seg=20),
cylinder('Tuner_post', 0.003, 0.0145, loc=(0, 0, 0), material=M_CHROME, seg=16),
box('Tuner_gearbox', (0.017, 0.016, 0.0105), loc=(0.004, 0, -0.0155 - 0.0105 + 0.0004), material=M_CHROME, base=True),
cylinder('Tuner_shaft', 0.0018, 0.022, rot=(0, math.pi / 2, 0), loc=(0.010, 0, -0.0205), material=M_CHROME, seg=12),
sphere('Tuner_button', 0.0105, loc=(0.035, 0, -0.0205), material=M_CHROME, seg=24, rings=12, scale=(0.72, 0.28, 0.95)),
]
bevel(tuner_parts[2], 0.002, 2, 40)
tuner = join(tuner_parts, 'Tuner_Treble_1')
smooth(tuner)
POSTS_WORLD = []
k = 0
for side in (-1, 1): # -1 = 저음 쪽(관람자 왼쪽)
for j, py in enumerate(POST_Y):
ob = tuner if k == 0 else duplicate(tuner)
ob.name = f"Tuner_{'Bass' if side < 0 else 'Treble'}_{j + 1}"
ob.matrix_world = head_m @ Matrix.Translation((side * POST_X, py, 0)) @ Matrix.Diagonal((side, 1, 1, 1))
G(ob)
POSTS_WORLD.append(head_m @ Vector((side * POST_X, py, 0.0105)))
k += 1
# ================================================================ 줄 6개
# 줄 순서: 0=6번(저음 E) … 5=1번(고음 E). 저음 3줄은 왼쪽 기둥, 고음 3줄은 오른쪽 기둥.
post_for = [POSTS_WORLD[2], POSTS_WORLD[1], POSTS_WORLD[0], POSTS_WORLD[3], POSTS_WORLD[4], POSTS_WORLD[5]]
radii = [0.00068, 0.00058, 0.00048, 0.00040, 0.00030, 0.00025]
for i in range(6):
xs, xn = STR_X_SADDLE[i], STR_X_NUT[i]
post = post_for[i]
pts = [(xs, Y_PIN, 0.0100), (xs, Y_SADDLE - 0.0006, FB_TOP + 0.0065 + 0.0000),
(xs, Y_SADDLE + 0.0006, FB_TOP + 0.0068),
(xn, Y_NUT + 0.0012, FB_TOP + 0.0029), (xn, Y_NUT + 0.0045, FB_TOP + 0.0022),
(post.x - 0.0028 * (1 if post.x > 0 else -1) * 0, post.y - 0.0, post.z)]
st = tube(f'String_{6 - i}', pts, radii[i], M_BRONZE if i < 4 else M_STEEL, bevel_res=1)
G(st)
# ================================================================ 세우기
pivot = bpy.data.objects.new('Guitar_Dreadnought', None)
link(pivot)
TILT = math.radians(75)
pivot.matrix_world = Matrix.Translation((0, 0, 0.075)) @ Matrix.Rotation(TILT, 4, 'X')
GM = pivot.matrix_world.copy()
for ob in GUITAR_PARTS:
ob.parent = pivot
def W(x, y, z):
return tuple(GM @ Vector((x, y, z)))
# ================================================================ 스탠드
TR = 0.0085
HUB = Vector((0, 0.34, 0.13))
# 받침 팔: 몸통 꼬리 쪽 아래 면을 따라가다 앞에서 위로 꺾인다
for side in (-1, 1):
x = side * 0.105
# 이 x 에서 몸통 윤곽의 꼬리쪽 y
yb = min(p.y for p in outline if abs(p.x - x) < 0.006 and p.y < 0.1)
off = TR + 0.0035
front = Vector(W(x, yb - off, 0.012))
under_a = Vector(W(x, yb - off, -0.004))
under_b = Vector(W(x, yb - off, -depth_at(yb) + 0.006))
back = Vector(W(x, yb - off - 0.01, -depth_at(yb) - 0.02))
pad = tube(f'Stand_Cradle_Pad_{"L" if side < 0 else "R"}',
[front + Vector((0, 0, 0.03)), front, under_a, under_b], TR + 0.002, M_RUBBER)
arm = tube(f'Stand_Cradle_Arm_{"L" if side < 0 else "R"}',
[under_b + (under_b - under_a).normalized() * -0.002, back, HUB + Vector((side * 0.01, 0, 0))],
TR, M_STAND)
# 목 받침(U자 요크) - 기타 좌표에서 만든다
YOKE_Y = 0.80
uk = []
for k in range(17):
a = math.pi * k / 16
uk.append(W(0.031 * math.cos(a), YOKE_Y, 0.004 - 0.0305 * math.sin(a)))
yoke = tube('Stand_Neck_Yoke', uk, 0.0055, M_RUBBER)
yoke_bottom = Vector(W(0, YOKE_Y, 0.004 - 0.0305 - 0.004))
tube('Stand_Mast', [yoke_bottom, yoke_bottom + Vector((0, 0.012, -0.03)), HUB], TR, M_STAND)
# 다리 셋과 고무발
feet = [Vector((-0.2, 0.06, 0.0)), Vector((0.2, 0.06, 0.0)), Vector((0, 0.58, 0.0))]
for i, f in enumerate(feet):
tube(f'Stand_Leg_{i + 1}', [HUB, f + Vector((0, 0, TR + 0.006))], TR, M_STAND)
sphere(f'Stand_Foot_{i + 1}', 0.016, loc=f + Vector((0, 0, 0.012)), material=M_RUBBER, seg=20, rings=10, scale=(1, 1, 0.75))
sphere('Stand_Hub', 0.02, loc=HUB, material=M_STAND, seg=24, rings=12)

다른 모형