본문으로 건너뛰기
3D 아틀리에
현대 포니 (1975) 3D 모형

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

이 모형에 대하여

포니는 현대자동차가 처음으로 독자 개발한 승용차로, 한국 자동차 산업이 조립 생산을 넘어 고유 모델을 갖게 된 출발점으로 꼽힌다. 차체 디자인은 이탈리아 이탈디자인의 조르제토 주지아로가 맡았고, 1974년 토리노 모터쇼에서 처음 공개된 뒤 1975년 말 울산 공장에서 양산에 들어갔다. 엔진과 변속기 등 주요 기계 부품은 미쓰비시의 기술을 들여와 썼다.

전장 4m가 채 되지 않는 작은 차체에 직선 위주의 각진 면, 길게 누운 뒷유리로 이어지는 패스트백 지붕선, 얇은 필러와 넓은 유리창이 특징이다. 앞면은 가로 살 그릴 양 끝에 둥근 헤드램프를 하나씩 넣었고, 가는 크롬 범퍼와 앞 펜더 위의 펜더 미러가 1970년대 차의 인상을 만든다.

이 모델은 초기형 4도어 패스트백을 실제 치수(전장 3.97m, 전폭 1.56m, 전고 1.36m, 휠베이스 2.34m)에 맞춰 재현했다. 둥근 헤드램프와 그릴, 앞문 삼각 환기창, 뒷문을 지나 쿼터 창으로 이어지는 옆 유리, 가로형 테일램프와 크롬 범퍼, 13인치 강철 휠과 크롬 허브캡을 표현했다. 차체 색은 당시 흔하던 겨자색 계열로 골랐다.

만든 방법

하부 차체는 길이 방향으로 16개 단면(모서리를 둥글린 다각형)을 정하고 보간해 이어 붙인 로프트 메시이며, 휠아치는 원통 불리언으로 파냈다. 캐빈은 앞유리·지붕·뒷유리 네 면으로 된 절두체에 베벨을 준 것이고, 옆 유리는 캐빈 옆면 위 사각형을 A·C 필러 선으로 잘라(서덜랜드-호지먼 클리핑) 얇은 판으로 올렸다. 헤드램프 렌즈와 타이어·휠은 회전체(lathe), 패널 분할선과 크롬 몰딩은 표면을 따라가는 가는 관(curve)이다. 바퀴 네 개는 하나의 메시를 공유하는 링크 복제본이다. 실내, 엔진룸, 서스펜션은 만들지 않았고 그릴과 엠블럼은 단순화했다.

build.py

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

# 현대 포니 (1975) — 주지아로가 그린 작고 각진 패스트백을 bpy 코드로만 만든 모델.
# 차체 하부는 길이 방향 단면(모서리를 둥글린 다각형)을 이어 붙인 로프트 메시이고,
# 둥근 휠아치는 원통 불리언으로 파냈다. 캐빈은 앞유리·지붕·길게 누운 뒷유리로 된
# 절두체에 베벨을 주었고, 옆 유리는 캐빈 옆면 위 다각형을 필러 선으로 잘라 얹었다.
# 둥근 헤드램프와 가로 살 그릴, 가는 크롬 범퍼, 펜더 미러, 가로형 테일램프는
# 박스·회전체·관(curve) 조합이며 바퀴 네 개는 메시를 공유하는 링크 복제본이다.
# 단위는 미터, 앞이 -Y. 전장 3.97 · 전폭 1.56 · 전고 1.36 · 휠베이스 2.34.
from atelier import *
SHOT = {"azimuth": -38, "elevation": 13, "lens": 55, "light": "studio", "background": "#2f3a47"}
# ── 재질 ───────────────────────────────────────────────
PAINT = mat("Paint_MustardYellow", "#d09a22", rough=0.32, metal=0.15, coat=1.0)
CHROME = mat("Chrome", "#d4d8dc", rough=0.12, metal=1.0)
TRIM = mat("Trim_BlackRubber", "#1c1d20", rough=0.6)
GLASS = mat("Glass_Tinted", "#1d2a30", rough=0.04, metal=0.2, coat=1.0)
LENS = mat("Headlamp_Lens", "#d5dde0", rough=0.05, metal=0.5)
RED = mat("Taillamp_Red", "#9a1418", rough=0.2)
AMBER = mat("Lamp_Amber", "#d47a10", rough=0.2)
TIRE = mat("Tire_Rubber", "#252527", rough=0.9)
STEEL = mat("Wheel_SteelSilver", "#9da2a8", rough=0.35, metal=0.8)
DARK = mat("Underbody_Dark", "#141517", rough=0.9)
PLATE = mat("Plate_White", "#e2e0d6", rough=0.5)
# ── 주요 치수 ───────────────────────────────────────────
Y_FRONT, Y_REAR = -1.985, 1.985
AXLE_F, AXLE_R = -1.255, 1.085
WHEEL_R = 0.289
HW = 0.779
BELT = 0.865
ROOF = 1.36
def set_sharp(ob, deg=35):
for p in ob.data.polygons:
p.use_smooth = True
ob.data.set_sharp_from_angle(angle=math.radians(deg))
return ob
def steps(a, b, n):
return [a + (b - a) * i / n for i in range(n + 1)]
# ── 1. 하부 차체: 단면 로프트 ─────────────────────────────
# (y, 윗면 높이 zt, 반폭 hw, 바닥 zb)
STATIONS = [
(-1.985, 0.730, 0.735, 0.300),
나머지 484줄 펼치기
(-1.979, 0.752, 0.758, 0.288),
(-1.962, 0.764, 0.771, 0.278),
(-1.925, 0.772, 0.777, 0.270),
(-1.800, 0.784, 0.779, 0.258),
(-1.400, 0.808, 0.779, 0.235),
(-1.000, 0.832, 0.779, 0.220),
(-0.620, 0.852, 0.779, 0.212),
(0.000, 0.862, 0.779, 0.212),
(0.800, 0.872, 0.779, 0.212),
(1.300, 0.882, 0.779, 0.222),
(1.700, 0.888, 0.776, 0.240),
(1.880, 0.886, 0.768, 0.258),
(1.950, 0.878, 0.756, 0.272),
(1.973, 0.866, 0.742, 0.282),
(1.985, 0.850, 0.722, 0.292),
]
def refine(st, sub=3):
out = []
for a, b in zip(st, st[1:]):
for i in range(sub):
t = i / sub
out.append(tuple(a[k] + (b[k] - a[k]) * t for k in range(4)))
out.append(st[-1])
return out
def section(zt, hw, zb):
"""오른쪽 반 단면 (x, z) — 아래 중앙에서 위 중앙까지."""
h = zt - zb
return [
(0.0, zb),
(hw - 0.09, zb),
(hw - 0.035, zb + 0.03),
(hw - 0.012, zb + 0.11),
(hw, zb + 0.30 * h),
(hw, zb + 0.66 * h), # 허리 캐릭터 라인
(hw - 0.008, zb + 0.70 * h),
(hw - 0.014, zt - 0.06),
(hw - 0.032, zt - 0.015),
(hw - 0.070, zt),
(hw * 0.5, zt + 0.010),
(0.0, zt + 0.014),
]
def build_lower_body():
bm = bmesh.new()
rings = []
for y, zt, hw, zb in refine(STATIONS):
half = section(zt, hw, zb)
full = half + [(-x, z) for x, z in reversed(half[1:-1])]
rings.append([bm.verts.new((x, y, z)) for x, z in full])
n = len(rings[0])
for a, b in zip(rings, rings[1:]):
for i in range(n):
bm.faces.new((a[i], a[(i + 1) % n], b[(i + 1) % n], b[i]))
bm.faces.new(rings[0])
bm.faces.new(list(reversed(rings[-1])))
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
me = bpy.data.meshes.new("Body_Lower")
bm.to_mesh(me)
bm.free()
ob = bpy.data.objects.new("Body_Lower", me)
link(ob)
assign(ob, PAINT)
ob.data.materials.append(DARK)
for ax in (AXLE_F, AXLE_R):
for s in (1, -1):
c = cylinder(f"Cutter_Arch_{'F' if ax < 0 else 'R'}{'R' if s > 0 else 'L'}",
r=0.345, h=0.34, loc=(s * (HW - 0.12), ax, WHEEL_R + 0.03),
rot=(0, math.pi / 2, 0), base=False, seg=48, material=DARK)
m = ob.modifiers.new("Arch", 'BOOLEAN')
m.object = c
m.solver = 'EXACT'
m.material_mode = 'TRANSFER'
c.hide_render = True
c.hide_viewport = True
c['atelier_cutter'] = True
apply_all(ob)
set_sharp(ob, 40)
return ob
body = build_lower_body()
def station_at(y):
y = max(STATIONS[0][0], min(STATIONS[-1][0], y))
for a, b in zip(STATIONS, STATIONS[1:]):
if a[0] <= y <= b[0]:
t = (y - a[0]) / (b[0] - a[0])
return [a[i] + (b[i] - a[i]) * t for i in (1, 2, 3)]
def surf_x(y, z):
"""오른쪽 옆면에서 (y, z) 의 x."""
zt, hw, zb = station_at(y)
pts = section(zt, hw, zb)[1:10]
for (x0, z0), (x1, z1) in zip(pts, pts[1:]):
if z0 <= z <= z1:
return x0 + (x1 - x0) * (z - z0) / (z1 - z0)
return hw
def surf_z(x, y):
"""윗면에서 (x, y) 의 z."""
zt, hw, zb = station_at(y)
ax = abs(x)
if ax <= hw * 0.5:
return zt + 0.014 - 0.004 * ax / (hw * 0.5)
return zt + 0.010 * (hw - 0.070 - ax) / (hw * 0.5 - 0.070)
# ── 2. 캐빈: 앞유리 · 지붕 · 길게 누운 패스트백 뒷유리 ─────
CAB_Y0, CAB_Y1 = -0.66, 1.66 # 벨트라인 높이에서 앞유리/뒷유리 밑단
ROOF_Y0, ROOF_Y1 = -0.10, 0.70 # 지붕 앞/뒤 끝
CAB_HW, ROOF_HW = 0.738, 0.600
CAB_Z0 = 0.80 # 차체 속에 묻히는 밑면
def side_x(z):
return CAB_HW - (z - BELT) * (CAB_HW - ROOF_HW) / (ROOF - BELT)
def a_line(z):
return CAB_Y0 + (z - BELT) * (ROOF_Y0 - CAB_Y0) / (ROOF - BELT)
def c_line(z):
return CAB_Y1 - (z - BELT) * (CAB_Y1 - ROOF_Y1) / (ROOF - BELT)
def build_cabin():
bm = bmesh.new()
zb = CAB_Z0
xb = side_x(zb)
B = [bm.verts.new(v) for v in ((-xb, a_line(zb), zb), (xb, a_line(zb), zb),
(xb, c_line(zb), zb), (-xb, c_line(zb), zb))]
T = [bm.verts.new(v) for v in ((-ROOF_HW, ROOF_Y0, ROOF), (ROOF_HW, ROOF_Y0, ROOF),
(ROOF_HW, ROOF_Y1, ROOF), (-ROOF_HW, ROOF_Y1, ROOF))]
bm.faces.new(list(reversed(B)))
bm.faces.new((B[0], B[1], T[1], T[0]))
bm.faces.new((B[1], B[2], T[2], T[1]))
bm.faces.new((B[2], B[3], T[3], T[2]))
bm.faces.new((B[3], B[0], T[0], T[3]))
bm.faces.new(T)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
me = bpy.data.meshes.new("Cabin")
bm.to_mesh(me)
bm.free()
ob = bpy.data.objects.new("Cabin_Greenhouse", me)
link(ob)
assign(ob, PAINT)
bevel(ob, width=0.04, segments=4, angle=15)
apply_all(ob)
set_sharp(ob, 50)
return ob
cabin = build_cabin()
# ── 3. 바퀴: 155SR13 타이어 + 강철 휠과 크롬 캡 ────────────
def build_wheel():
tw = 0.078
prof = [(0.165, -tw + 0.012), (0.190, -tw), (0.255, -tw - 0.004), (0.278, -tw + 0.012),
(WHEEL_R, -tw + 0.035), (WHEEL_R, tw - 0.035), (0.278, tw - 0.012), (0.255, tw + 0.004),
(0.190, tw), (0.165, tw - 0.012)]
tire = lathe("Tire", prof, material=TIRE, seg=96)
rim = [(0.0, 0.010), (0.11, 0.012), (0.13, 0.018), (0.150, 0.020), (0.168, 0.028),
(0.172, 0.022), (0.172, -0.03), (0.0, -0.03)]
disc = lathe("Wheel_Disc", rim, material=STEEL, seg=96)
cap_prof = [(0.0, 0.050), (0.04, 0.048), (0.075, 0.040), (0.095, 0.026), (0.102, 0.012), (0.102, 0.0)]
cap = lathe("Hubcap", cap_prof, material=CHROME, seg=64)
parts = [tire, disc, cap]
for i in range(6):
a = 2 * math.pi * i / 6 + math.pi / 6
s = box(f"Wheel_Vent_{i}", (0.03, 0.022, 0.006),
loc=(0.130 * math.cos(a), 0.130 * math.sin(a), 0.0175), rot=(0, 0, a), material=DARK)
parts.append(s)
w = join(parts, "Wheel_FR")
for p in w.data.polygons:
p.use_smooth = True
w.data.set_sharp_from_angle(angle=math.radians(40))
return w
wheel = build_wheel()
TRACK_X = 0.648
wheel.rotation_euler = (0, math.pi / 2, 0)
wheel.location = (TRACK_X, AXLE_F, WHEEL_R)
duplicate(wheel, loc=(TRACK_X, AXLE_R, WHEEL_R), name="Wheel_RR")
duplicate(wheel, loc=(-TRACK_X, AXLE_F, WHEEL_R), rot=(0, -math.pi / 2, 0), name="Wheel_FL")
duplicate(wheel, loc=(-TRACK_X, AXLE_R, WHEEL_R), rot=(0, -math.pi / 2, 0), name="Wheel_RL")
# 바퀴 사이로 비치는 하부(어두운 판) — 바퀴 안쪽에서 차체 바닥에 붙는다
box("Underbody_Floor", (1.10, 3.30, 0.06), loc=(0, -0.05, 0.215), material=DARK)
# ── 4. 유리: 캐빈 면 위에 얇은 판 ────────────────────────
def _slab_obj(name, bm, material):
me = bpy.data.meshes.new(name)
bm.to_mesh(me)
bm.free()
ob = bpy.data.objects.new(name, me)
link(ob)
return assign(ob, material)
def slab(name, pts, normal, material, off=0.004, th=0.006):
n = Vector(normal).normalized()
bm = bmesh.new()
lo = [bm.verts.new(Vector(p) + n * off) for p in pts]
hi = [bm.verts.new(Vector(p) + n * (off + th)) for p in pts]
bm.faces.new(list(reversed(lo)))
bm.faces.new(hi)
k = len(pts)
for i in range(k):
bm.faces.new((lo[i], lo[(i + 1) % k], hi[(i + 1) % k], hi[i]))
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
return _slab_obj(name, bm, material)
def clip(poly, f):
"""(y,z) 다각형을 f(y,z) >= 0 인 쪽만 남긴다(서덜랜드-호지먼)."""
out = []
for i in range(len(poly)):
p, q = poly[i], poly[(i + 1) % len(poly)]
fp, fq = f(*p), f(*q)
if fp >= 0:
out.append(p)
if (fp >= 0) != (fq >= 0):
t = fp / (fp - fq)
out.append((p[0] + (q[0] - p[0]) * t, p[1] + (q[1] - p[1]) * t))
return out
def window(y0, y1, z0, z1, fa, fc):
"""사각 창을 A 필러(앞)·C 필러(뒤) 선에서 각각 fa, fc 만큼 안쪽으로 자른다."""
poly = [(y0, z0), (y1, z0), (y1, z1), (y0, z1)]
poly = clip(poly, lambda y, z: y - (a_line(z) + fa))
poly = clip(poly, lambda y, z: (c_line(z) - fc) - y)
return poly
def side_pt(s, y, z):
return (s * side_x(z), y, z)
SIDE_N = (ROOF - BELT, 0, CAB_HW - ROOF_HW)
WZ0, WZ1 = 0.905, 1.315
B_PILLAR = (0.10, 0.17)
for s, tag in ((1, "R"), (-1, "L")):
nrm = (s * SIDE_N[0], 0, SIDE_N[2])
frame = window(-1.0, 2.0, WZ0 - 0.015, WZ1 + 0.015, 0.06, 0.07)
slab(f"Window_Frame_{tag}", [side_pt(s, y, z) for y, z in frame], nrm, TRIM, off=0.001, th=0.003)
wins = {
"Front": window(-1.0, B_PILLAR[0], WZ0, WZ1, 0.08, 0.09),
"Rear": window(B_PILLAR[1], 0.80, WZ0, WZ1, 0.08, 0.09),
"Quarter": window(0.86, 2.0, WZ0, WZ1, 0.08, 0.16),
}
for k, poly in wins.items():
slab(f"Glass_Side_{k}_{tag}", [side_pt(s, y, z) for y, z in poly], nrm, GLASS, off=0.003, th=0.004)
# 앞문 삼각 환기창(벤트 윈도) 칸막이: 앞유리 위 세로 크롬 막대
vy0, vy1 = -0.30, -0.03
vb = [Vector(side_pt(s, vy0, WZ0)) + Vector(nrm).normalized() * 0.009,
Vector(side_pt(s, vy1, WZ1 - 0.005)) + Vector(nrm).normalized() * 0.009]
curve_tube(f"Vent_Window_Divider_{tag}", [tuple(p) for p in vb], radius=0.007, material=CHROME,
resolution=2, bevel_res=2)
# 창틀 크롬 테두리(위쪽 지붕선을 따라)
zt = WZ1 + 0.02
yz = [(a_line(WZ0) + 0.055, WZ0), (a_line(zt) + 0.055, zt), (c_line(zt) - 0.065, zt), (c_line(WZ0) - 0.065, WZ0)]
pts = [Vector(side_pt(s, y, z)) + Vector(nrm).normalized() * 0.006 for y, z in yz]
curve_tube(f"Window_Chrome_{tag}", [tuple(p) for p in pts], radius=0.006, material=CHROME,
resolution=2, bevel_res=2)
def screen(name, yb, yt, sign, zb_, zt_, inset=0.07):
def y_at(z):
return yb + (z - BELT) * (yt - yb) / (ROOF - BELT)
def hw_at(z):
return side_x(z) - inset
pts = [(-hw_at(zb_), y_at(zb_), zb_), (hw_at(zb_), y_at(zb_), zb_),
(hw_at(zt_), y_at(zt_), zt_), (-hw_at(zt_), y_at(zt_), zt_)]
dy, dz = (yt - yb), (ROOF - BELT)
nrm = (0, -dz, dy) if sign > 0 else (0, dz, -dy)
slab(name, pts, nrm, GLASS, off=0.003, th=0.004)
screen("Glass_Windshield", CAB_Y0, ROOF_Y0, 1, BELT + 0.02, ROOF - 0.04)
screen("Glass_Rear", CAB_Y1, ROOF_Y1, -1, 0.935, ROOF - 0.045, inset=0.09)
# ── 5. 패널 분할선 ─────────────────────────────────────
GROOVE = 0.0045
def groove(name, pts):
return curve_tube(name, pts, radius=GROOVE, material=DARK, resolution=2, bevel_res=1)
def side_line(name, yz, s=1):
return groove(name, [(s * surf_x(y, z), y, z) for y, z in yz])
def top_line(name, xy):
return groove(name, [(x, y, surf_z(x, y)) for x, y in xy])
top_line("Line_Hood_Rear", [(x, -0.72) for x in steps(-0.70, 0.70, 16)])
top_line("Line_Hood_Front", [(x, -1.93) for x in steps(-0.70, 0.70, 16)])
top_line("Line_Trunk_Front", [(x, 1.70) for x in steps(-0.66, 0.66, 14)])
for s, tag in ((1, "R"), (-1, "L")):
top_line(f"Line_Hood_Side_{tag}", [(s * 0.70, y) for y in steps(-1.93, -0.72, 16)])
top_line(f"Line_Trunk_Side_{tag}", [(s * 0.66, y) for y in steps(1.70, 1.94, 4)])
side_line(f"Line_Door_Front_{tag}", [(-0.66, z) for z in steps(0.27, 0.86, 10)], s)
side_line(f"Line_Door_B_{tag}", [(0.13, z) for z in steps(0.24, 0.86, 10)], s)
# 뒷문 뒤끝은 세로로 내려오다 휠아치를 따라 돈다
ARC_R = 0.372
a0 = math.degrees(math.acos((0.86 - AXLE_R) / ARC_R))
arc = [(0.86, 0.86)]
for a in steps(a0, 178, 10):
r = math.radians(a)
arc.append((AXLE_R + ARC_R * math.cos(r), WHEEL_R + 0.03 + ARC_R * math.sin(r)))
arc.append((arc[-1][0], 0.27))
side_line(f"Line_Door_Rear_{tag}", arc, s)
# ── 6. 가는 크롬 범퍼(앞뒤 같은 모양) ─────────────────────
def bumper_outline():
outer = [(0.800, -1.76), (0.800, -1.90), (0.775, -1.990), (0.690, -2.030), (0.0, -2.040)]
inner = [(0.0, -1.982), (0.660, -1.975), (0.740, -1.955), (0.768, -1.90), (0.768, -1.76)]
right_outer = outer
left_outer = [(-x, y) for x, y in reversed(outer[:-1])]
left_inner = [(-x, y) for x, y in reversed(inner[1:])]
right_inner = inner
return right_outer + left_outer + left_inner + right_inner
def build_bumper(name, z0):
bar = prism(name + "_Chrome", bumper_outline(), 0.10, loc=(0, 0, z0), material=CHROME)
bevel(bar, width=0.014, segments=3, angle=25)
apply_all(bar)
strip = prism(name + "_Rubber", [(x * 1.004, y - (0.006 if y < -1.95 else 0)) for x, y in bumper_outline()],
0.026, loc=(0, 0, z0 + 0.038), material=TRIM)
b = join([bar, strip], name)
set_sharp(b, 40)
return b
BUMP_Z = 0.37
front_bumper = build_bumper("Bumper_Front", BUMP_Z)
rear_bumper = duplicate(front_bumper, rot=(0, 0, math.pi), name="Bumper_Rear")
# 범퍼 오버라이더(세로 고무 블록)
for s, tag in ((1, "R"), (-1, "L")):
for yy, rz, nm in ((-2.045, 0, "Front"), (2.045, math.pi, "Rear")):
ov = box(f"Overrider_{nm}_{tag}", (0.05, 0.05, 0.15), loc=(s * 0.42, yy, BUMP_Z + 0.05), material=TRIM)
bevel(ov, 0.015, 3)
# ── 7. 앞모습: 가로 살 그릴 · 둥근 헤드램프 · 방향지시등 ─────
YF = Y_FRONT
GZ = 0.615
box("Grille_Back", (1.30, 0.03, 0.175), loc=(0, YF - 0.004, GZ), material=DARK)
for i, z in enumerate(steps(GZ - 0.066, GZ + 0.066, 6)):
box(f"Grille_Bar_{i}", (0.85, 0.014, 0.008), loc=(0, YF - 0.020, z), material=CHROME)
for i, (sz, lc) in enumerate((((1.32, 0.02, 0.012), (0, YF - 0.016, GZ + 0.092)),
((1.32, 0.02, 0.012), (0, YF - 0.016, GZ - 0.092)))):
box(f"Grille_Frame_{i}", sz, loc=lc, material=CHROME)
for s, tag in ((1, "R"), (-1, "L")):
hx = s * 0.545
ring = cylinder(f"Headlamp_Ring_{tag}", r=0.094, h=0.03, loc=(hx, YF - 0.012, GZ),
rot=(math.pi / 2, 0, 0), base=False, material=CHROME, seg=40)
lens = lathe(f"Headlamp_Lens_{tag}", [(0.0, 0.022), (0.04, 0.019), (0.07, 0.012), (0.084, 0.0)],
loc=(hx, YF - 0.026, GZ), rot=(math.pi / 2, 0, 0), material=LENS, seg=40)
box(f"Grille_Divider_{tag}", (0.012, 0.022, 0.18), loc=(s * 0.432, YF - 0.016, GZ), material=CHROME)
tl = box(f"Turn_Signal_{tag}", (0.15, 0.03, 0.06), loc=(s * 0.56, YF - 0.002, 0.505), material=AMBER)
bevel(tl, 0.008, 2)
sm = box(f"Side_Marker_{tag}", (0.02, 0.07, 0.035),
loc=(s * (surf_x(-1.80, 0.58) + 0.002), -1.80, 0.58), material=AMBER)
bevel(sm, 0.005, 2)
# 옛 현대 'HD' 타원 엠블럼
emb = torus("Emblem_Hyundai_HD", R=0.05, r=0.006, loc=(0, YF - 0.032, GZ),
rot=(math.pi / 2, 0, 0), material=CHROME, seg=40, rseg=8)
emb.scale = (1.0, 0.7, 1.0)
box("Emblem_Back", (0.09, 0.006, 0.055), loc=(0, YF - 0.029, GZ), material=TRIM)
hd = text3d("Emblem_Letters", "HD", size=0.045, depth=0.002, loc=(0, YF - 0.033, GZ - 0.016),
rot=(math.pi / 2, 0, 0), material=CHROME)
box("Plate_Front", (0.32, 0.012, 0.11), loc=(0, -2.046, BUMP_Z + 0.03), material=PLATE)
# ── 8. 뒷모습: 가로 테일램프 · 번호판 · PONY 배지 ────────────
YR = Y_REAR
TZ = 0.69
for s, tag in ((1, "R"), (-1, "L")):
rim = box(f"Taillamp_Housing_{tag}", (0.40, 0.022, 0.175), loc=(s * 0.52, YR + 0.006, TZ), material=CHROME)
bevel(rim, 0.005, 2)
for k, (x0, x1, m) in enumerate(((0.57, 0.71, RED), (0.44, 0.57, AMBER), (0.33, 0.44, LENS))):
box(f"Taillamp_{tag}{k}", (x1 - x0 - 0.008, 0.012, 0.155), loc=(s * (x0 + x1) / 2, YR + 0.018, TZ),
material=m)
for j, dz in enumerate((-0.045, -0.015, 0.015, 0.045)):
box(f"Taillamp_Rib_{tag}{k}{j}", (x1 - x0 - 0.01, 0.004, 0.004),
loc=(s * (x0 + x1) / 2, YR + 0.025, TZ + dz), material=CHROME if m is LENS else m)
box("Rear_Panel_Trim", (0.64, 0.012, 0.012), loc=(0, YR + 0.004, TZ + 0.08), material=CHROME)
box("Plate_Recess", (0.38, 0.012, 0.17), loc=(0, YR + 0.004, 0.565), material=TRIM)
box("Plate_Rear", (0.32, 0.012, 0.15), loc=(0, YR + 0.011, 0.565), material=PLATE)
text3d("Badge_PONY", "PONY", size=0.055, depth=0.003, loc=(-0.55, YR + 0.004, 0.79),
rot=(math.pi / 2, 0, math.pi), material=CHROME)
text3d("Badge_HYUNDAI", "HYUNDAI", size=0.035, depth=0.002, loc=(0.52, YR + 0.004, 0.79),
rot=(math.pi / 2, 0, math.pi), material=CHROME)
cylinder("Exhaust_Pipe", r=0.024, h=0.36, loc=(0.45, 1.87, 0.245), rot=(-math.pi / 2, 0, 0),
base=False, material=CHROME, seg=20)
# ── 9. 측면 디테일: 몰딩 · 손잡이 · 펜더 미러 · 와이퍼 · 레인 몰딩 ──
for s, tag in ((1, "R"), (-1, "L")):
for k, y in enumerate((-0.10, 0.70)):
box(f"Door_Handle_{tag}{k}", (0.016, 0.12, 0.024), loc=(s * (surf_x(y, 0.79) + 0.004), y, 0.79),
material=CHROME)
y0, y1 = a_line(BELT) + 0.06, 0.86
box(f"Belt_Molding_{tag}", (0.010, y1 - y0, 0.012),
loc=(s * (side_x(BELT + 0.012) + 0.003), (y0 + y1) / 2, BELT + 0.012), material=CHROME)
box(f"Drip_Rail_{tag}", (0.010, ROOF_Y1 - ROOF_Y0 - 0.02, 0.012),
loc=(s * (ROOF_HW + 0.002), (ROOF_Y0 + ROOF_Y1) / 2, ROOF - 0.03), material=CHROME)
# 휠아치 크롬 테두리 대신 문 아래 얇은 사이드 실 몰딩
yc0, yc1 = AXLE_F + 0.40, AXLE_R - 0.40
box(f"Sill_Molding_{tag}", (0.012, yc1 - yc0, 0.016),
loc=(s * (surf_x((yc0 + yc1) / 2, 0.32) + 0.004), (yc0 + yc1) / 2, 0.32), material=CHROME)
# 펜더 미러: 앞 펜더 위에 선 크롬 기둥과 둥근 거울
mx, my = s * 0.69, -1.05
mz = surf_z(mx, my)
cylinder(f"Fender_Mirror_Stem_{tag}", r=0.009, h=0.12, loc=(mx, my, mz - 0.01), material=CHROME, seg=12)
cylinder(f"Fender_Mirror_Base_{tag}", r=0.022, h=0.016, loc=(mx, my, mz - 0.006), material=TRIM, seg=16)
head = cylinder(f"Fender_Mirror_Head_{tag}", r=0.05, h=0.035, loc=(mx, my + 0.006, mz + 0.14),
rot=(math.pi / 2, 0, s * math.radians(12)), base=False, material=CHROME, seg=24)
bevel(head, 0.01, 2)
cylinder(f"Fender_Mirror_Glass_{tag}", r=0.042, h=0.004, loc=(mx + s * 0.004, my + 0.026, mz + 0.14),
rot=(math.pi / 2, 0, s * math.radians(12)), base=False, material=LENS, seg=24)
ws_ang = math.atan2(ROOF - BELT, ROOF_Y0 - CAB_Y0)
for k, x in enumerate((-0.30, 0.20)):
z = 0.935
n = Vector((0, -math.sin(ws_ang), math.cos(ws_ang)))
p = Vector((x, a_line(z), z)) + n * 0.014
box(f"Wiper_{k}", (0.44, 0.012, 0.012), loc=p, rot=(ws_ang, 0, math.radians(4)), material=TRIM)
box("Cowl_Panel", (1.36, 0.10, 0.012), loc=(0, a_line(BELT) - 0.05, surf_z(0, -0.71) + 0.002), material=TRIM)
# 지붕 앞뒤 크롬 몰딩(앞유리·뒷유리 위)
def screen_surround(name, yb, yt, sign, zb_, zt_, inset):
"""앞유리·뒷유리 둘레 크롬 몰딩: 유리 가장자리를 따라 닫힌 관."""
def y_at(z):
return yb + (z - BELT) * (yt - yb) / (ROOF - BELT)
dy, dz = (yt - yb), (ROOF - BELT)
n = Vector((0, -dz, dy) if sign > 0 else (0, dz, -dy)).normalized()
pts = []
for x0, z0 in ((-1, zb_), (1, zb_), (1, zt_), (-1, zt_)):
p = Vector((x0 * (side_x(z0) - inset), y_at(z0), z0)) + n * 0.006
pts.append(tuple(p))
curve_tube(name, pts, radius=0.007, material=CHROME, closed=True, resolution=2, bevel_res=2)
screen_surround("Windshield_Chrome", CAB_Y0, ROOF_Y0, 1, BELT + 0.02, ROOF - 0.04, 0.07)
screen_surround("Rear_Glass_Chrome", CAB_Y1, ROOF_Y1, -1, 0.935, ROOF - 0.045, 0.09)
# 앞 펜더 라디오 안테나
cylinder("Radio_Antenna", r=0.004, h=0.55, loc=(-0.70, -0.80, surf_z(-0.70, -0.80) - 0.01),
rot=(math.radians(-8), 0, 0), material=CHROME, seg=8)
# ── 10. 마무리: 커브·글자를 메시로 ──────────────────────
def to_mesh(ob):
dg = bpy.context.evaluated_depsgraph_get()
me = bpy.data.meshes.new_from_object(ob.evaluated_get(dg))
new = bpy.data.objects.new(ob.name, me)
link(new)
new.matrix_world = ob.matrix_world.copy()
bpy.data.objects.remove(ob, do_unlink=True)
return new
for ob in list(bpy.context.scene.objects):
if ob.type in ('CURVE', 'FONT'):
to_mesh(ob)

다른 모형