
끌어서 회전, 휠·두 손가락으로 확대
이 모형에 대하여
포니는 현대자동차가 처음으로 독자 개발한 승용차로, 한국 자동차 산업이 조립 생산을 넘어 고유 모델을 갖게 된 출발점으로 꼽힌다. 차체 디자인은 이탈리아 이탈디자인의 조르제토 주지아로가 맡았고, 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.985AXLE_F, AXLE_R = -1.255, 1.085WHEEL_R = 0.289HW = 0.779BELT = 0.865ROOF = 1.36def 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 obdef 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 outdef 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 obbody = 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 hwdef 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.600CAB_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 obcabin = 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 wwheel = build_wheel()TRACK_X = 0.648wheel.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 outdef 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 polydef 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.315B_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.0045def 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_innerdef 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 bBUMP_Z = 0.37front_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_FRONTGZ = 0.615box("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_REARTZ = 0.69for 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 newfor ob in list(bpy.context.scene.objects): if ob.type in ('CURVE', 'FONT'): to_mesh(ob)






