본문으로 건너뛰기
3D 아틀리에
윌리스 MB 지프 (1941) 3D 모형

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

이 모형에 대하여

윌리스 MB는 미 육군의 1/4톤 4x4 정찰·연락용 차량 요구에 따라 윌리스-오버랜드가 생산한 소형 군용차다. 1941년부터 1945년까지 윌리스와 포드(GPW)가 거의 같은 설계로 60만 대 넘게 만들었고, 연합군 각 전선에서 쓰였다. 오늘날 '지프'라는 이름과 SUV의 원형으로 널리 알려져 있다.

평평한 보닛과 펜더, 문이 없는 상자형 차체, 세로 슬롯이 나란한 철판 그릴, 앞으로 눕힐 수 있는 2분할 앞유리가 특징이다. 차체 뒤에는 예비 타이어와 연료통(제리캔)을, 운전석 쪽 옆면에는 삽과 도끼를 달았다. 보닛의 흰 별은 연합군 식별 표지다.

이 모델은 앞유리를 세우고 캔버스 지붕을 뒤로 접어 내린 상태를 실제 크기(전장 약 3.3m, 휠베이스 약 2.03m)로 재현했다. 올리브 드랩 도장, V자 트레드 타이어, 분할식 강철 휠, 등화관제등과 견인 고리까지 표현했다.

만든 방법

차체 옆면은 문 컷아웃과 뒷바퀴 아치를 포함한 옆모습 다각형을 얇게 돌출해 만들고, 위 테두리는 같은 점열을 따라가는 관으로 감쌌다. 그릴은 앞모습 윤곽 판에 9개의 슬롯과 전조등 구멍을 불리언으로 뚫었다. 타이어는 단면을 회전체로 돌린 뒤 트레드 블록 48개를 각도별로 배치했고, 이 바퀴 메시 하나를 다섯 바퀴(예비 포함)가 공유한다. 엔진, 프레임, 차축, 스프링 같은 하부 구조는 단순한 상자와 원기둥으로 대신했고, 좌석과 접힌 지붕도 단순화했다. 보닛 번호는 실제 특정 차량의 번호가 아니라 예시다.

build.py

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

# 윌리스 MB 지프 (1941~1945, 미 육군 1/4톤 4x4 정찰차)
# - 차체는 옆모습 단면(문 없는 곡선 컷아웃, 뒷바퀴 아치)을 X 방향으로 돌출한 얇은 판들로 짜 맞췄다.
# - 9칸 세로 슬롯 그릴은 앞모습 윤곽을 돌출한 판에서 슬롯과 전조등 구멍을 불리언으로 뚫었다.
# - 바퀴는 타이어 단면을 회전체(lathe)로 돌리고 V자 트레드 블록을 수학 배치한 뒤, 같은 메시를 5개(예비 포함) 공유한다.
# - 접이식 앞유리, 버킷 시트, 운전대, 삽, 제리캔, 보닛의 흰 별 같은 식별 요소는 상자·원기둥·관으로 덧붙였다.
# 단위 m, 실제 크기(전장 약 3.3m, 휠베이스 2.03m). 앞쪽이 -Y, 운전석(좌핸들)은 +X 쪽이다.
import math
from atelier import *
SHOT = {"azimuth": -40, "elevation": 13, "lens": 55, "light": "day", "background": "#28303a"}
# ── 재질 ─────────────────────────────────────────────
OD = mat("OliveDrab_Paint", "#4d5330", 0.62, 0.0)
OD_DARK = mat("OliveDrab_Shadowed", "#3a3f25", 0.7, 0.0)
RUBBER = mat("Tire_Rubber", "#262624", 0.88, 0.0)
STEEL = mat("Dark_Steel", "#3b3b37", 0.5, 0.7)
STAR = mat("Star_White_Stencil", "#dcd8c6", 0.6, 0.0)
GLASS = glass("Windshield_Glass", "#a9c3cc", 0.04)
LENS = mat("Headlamp_Lens", "#d9dccf", 0.12, 0.3)
CHROME = mat("Lamp_Rim_Steel", "#9a9a92", 0.25, 1.0)
CANVAS = mat("Seat_Canvas", "#4a4630", 0.85, 0.0)
RED = mat("Tail_Lens_Red", "#7d1c14", 0.3, 0.0)
WOOD = mat("Tool_Handle_Wood", "#5e4630", 0.7, 0.0)
# ── 치수 ─────────────────────────────────────────────
TIRE_R = 0.36
AXLE_Z = TIRE_R
Y_FA, Y_RA = -1.10, 0.93 # 앞/뒤 차축(휠베이스 2.03m)
TRACK = 0.6225 # 윤거 1.245m 의 절반
TUB_HALF = 0.72
FLOOR_Z = 0.50
GRILLE_Y = -1.31
HOOD_TOP = 1.05
FENDER_TOP = 0.88
def arc_pts(cy, cz, r, a0, a1, n):
return [(cy + r * math.cos(math.radians(a0 + (a1 - a0) * i / n)),
cz + r * math.sin(math.radians(a0 + (a1 - a0) * i / n))) for i in range(n + 1)]
def bake(ob):
"""join 후 오브젝트 변환을 메시에 굽고 원점을 (0,0,0) 으로 되돌린다."""
ob.data.transform(ob.matrix_basis)
ob.matrix_basis = Matrix.Identity(4)
return ob
나머지 320줄 펼치기
def side_profile(name, pts, x_center, thick, material):
"""옆모습 (y,z) 다각형을 X 방향 두께 thick 으로 돌출."""
return extrude_profile(name, pts, thick, loc=(x_center, 0, 0), material=material, axis='X')
# ── 바퀴 (하나 만들고 링크 복제) ─────────────────────
def make_wheel():
W = 0.088
prof = [(0.205, -W + 0.005), (0.25, -W - 0.004), (0.31, -W - 0.002), (0.345, -W + 0.012),
(0.358, -W + 0.03), (0.36, 0.0), (0.358, W - 0.03), (0.345, W - 0.012), (0.31, W + 0.002),
(0.25, W + 0.004), (0.205, W - 0.005), (0.20, 0.0), (0.205, -W + 0.005)]
tire = lathe("Tire", prof, material=RUBBER, seg=72)
parts = [tire]
n = 24
lug = box("Lug", (0.022, 0.05, 0.07), material=RUBBER)
bevel(lug, 0.006, 1)
apply_all(lug)
for i in range(n):
for side in (-1, 1):
a = 2 * math.pi * (i + (0.5 if side > 0 else 0)) / n
r = 0.362
o = duplicate(lug, loc=(r * math.cos(a), r * math.sin(a), side * 0.036),
rot=(side * math.radians(28), 0, a), linked=False)
parts.append(o)
bpy.data.objects.remove(lug, do_unlink=True)
# 강철 휠(분할 림): 바깥쪽 = +Z
rim_prof = [(0.0, 0.035), (0.055, 0.035), (0.07, 0.048), (0.115, 0.048), (0.13, 0.03), (0.17, 0.028),
(0.19, 0.04), (0.205, 0.07), (0.214, 0.075), (0.214, -0.075), (0.205, -0.07), (0.19, -0.04),
(0.17, 0.018), (0.13, 0.02), (0.07, 0.025), (0.0, 0.025)]
rim = lathe("Rim", rim_prof, material=OD, seg=48)
parts.append(rim)
for k in range(5):
a = 2 * math.pi * k / 5
nut = cylinder("Nut", 0.011, 0.016, loc=(0.092 * math.cos(a), 0.092 * math.sin(a), 0.046),
material=STEEL, seg=6)
parts.append(nut)
for k in range(8): # 분할 림 볼트
a = 2 * math.pi * (k + 0.5) / 8
parts.append(cylinder("RimBolt", 0.008, 0.012, loc=(0.15 * math.cos(a), 0.15 * math.sin(a), 0.026),
material=STEEL, seg=6))
parts.append(cylinder("HubCap", 0.045, 0.03, loc=(0, 0, 0.046), r2=0.035, material=OD_DARK, seg=24))
return join(parts, "Wheel")
wheel = make_wheel()
wheel.location = (TRACK, Y_FA, AXLE_Z)
wheel.rotation_euler = (0, math.pi / 2, 0)
wheel.name = "Wheel_FrontLeft"
duplicate(wheel, loc=(-TRACK, Y_FA, AXLE_Z), rot=(0, -math.pi / 2, 0), name="Wheel_FrontRight")
duplicate(wheel, loc=(TRACK, Y_RA, AXLE_Z), rot=(0, math.pi / 2, 0), name="Wheel_RearLeft")
duplicate(wheel, loc=(-TRACK, Y_RA, AXLE_Z), rot=(0, -math.pi / 2, 0), name="Wheel_RearRight")
# ── 프레임·차축·스프링 ──────────────────────────────
for sx in (-1, 1):
box(f"Frame_Rail_{'L' if sx > 0 else 'R'}", (0.06, 3.02, 0.11), loc=(sx * 0.42, -0.10, 0.40),
material=STEEL, base=True)
for ya in (Y_FA, Y_RA):
box("Leaf_Spring", (0.05, 0.92, 0.035), loc=(sx * 0.42, ya, 0.368), material=STEEL, base=True)
for ya, nm in ((Y_FA, "Front"), (Y_RA, "Rear")):
cylinder(f"Axle_{nm}", 0.045, 1.12, loc=(0, ya, AXLE_Z), rot=(0, math.pi / 2, 0), material=STEEL,
base=False, seg=20)
sphere(f"Differential_{nm}", 0.12, loc=(-0.12, ya, AXLE_Z), material=STEEL, seg=24, rings=12,
scale=(0.8, 1.0, 1.0))
box("Frame_Crossmember_Front", (0.90, 0.08, 0.10), loc=(0, -1.52, 0.40), material=STEEL, base=True)
cylinder("Driveshaft", 0.03, 1.9, loc=(-0.12, -0.08, 0.38), rot=(math.pi / 2, 0, 0), material=STEEL,
base=False, seg=12)
cylinder("Transfer_Case", 0.11, 0.30, loc=(-0.12, -0.15, 0.42), rot=(0, math.pi / 2, 0), material=STEEL,
base=False, seg=16)
# ── 앞 범퍼 ────────────────────────────────────────
bumper = box("Bumper_Front", (1.50, 0.07, 0.15), loc=(0, -1.60, 0.38), material=OD, base=True)
bevel(bumper, 0.01, 1)
for sx in (-1, 1):
torus("Tow_Hook", 0.045, 0.012, loc=(sx * 0.40, -1.655, 0.455), rot=(0, math.pi / 2, 0),
material=STEEL, seg=20, rseg=8)
box("Bumper_Bracket", (0.06, 0.06, 0.10), loc=(sx * 0.42, -1.545, 0.40), material=STEEL, base=True)
# ── 그릴 (9칸 슬롯 + 전조등 구멍) ─────────────────
GW = 0.50
gpts = [(-GW, 0.50), (GW, 0.50), (GW, 0.93)] + \
[(x, z) for x, z in arc_pts(GW - 0.12, 0.93, 0.12, 0, 90, 8)[1:]] + \
[(x, z) for x, z in arc_pts(-GW + 0.12, 0.93, 0.12, 90, 180, 8)[:-1]] + [(-GW, 0.93)]
grille = extrude_profile("Grille", gpts, 0.03, loc=(0, GRILLE_Y, 0), material=OD)
cutters = []
for i in range(9):
x = (i - 4) * 0.066
c = box("SlotCut", (0.040, 0.2, 0.40), loc=(x, GRILLE_Y, 0.585), material=None, base=True)
bevel(c, 0.019, 4, angle=10)
apply_all(c)
cutters.append(c)
for sx in (-1, 1):
cutters.append(cylinder("LampCut", 0.092, 0.2, loc=(sx * 0.40, GRILLE_Y, 0.76), rot=(math.pi / 2, 0, 0),
base=False, seg=32))
cut = join(cutters, "Grille_Cutter")
boolean(grille, cut)
box("Radiator_Core", (0.62, 0.05, 0.44), loc=(0, GRILLE_Y + 0.07, 0.57), material=STEEL, base=True)
for sx in (-1, 1):
bucket = cylinder("Headlamp_Bucket", 0.094, 0.10, loc=(sx * 0.40, GRILLE_Y + 0.11, 0.76),
rot=(math.pi / 2, 0, 0), material=OD_DARK, seg=32)
lathe("Headlamp_Lens", [(0.0, -0.012), (0.05, -0.008), (0.078, 0.0), (0.084, 0.008)],
loc=(sx * 0.40, GRILLE_Y + 0.025, 0.76), rot=(math.pi / 2, 0, 0), material=LENS, seg=32)
torus("Headlamp_Rim", 0.086, 0.008, loc=(sx * 0.40, GRILLE_Y - 0.017, 0.76), rot=(math.pi / 2, 0, 0),
material=CHROME, seg=32, rseg=8)
# ── 보닛 ───────────────────────────────────────────
hood = box("Hood", (0.96, 1.03, HOOD_TOP - FENDER_TOP), loc=(0, -0.805, FENDER_TOP), material=OD, base=True)
bevel(hood, 0.018, 3)
for sx in (-1, 1):
# 보닛 고정 고리
box("Hood_Latch", (0.012, 0.03, 0.07), loc=(sx * 0.486, -1.05, 0.92), material=STEEL, base=True)
box("Hood_Hinge", (0.90, 0.025, 0.015), loc=(0, -0.30, HOOD_TOP - 0.004), material=STEEL, base=True)
# ── 앞 펜더 (평평한 윗면, 뒤끝이 발판으로 꺾임) ───
fender_top = [(-1.335, 0.81), (-1.315, FENDER_TOP), (-0.58, FENDER_TOP), (-0.47, 0.85), (-0.38, 0.76),
(-0.32, 0.62), (-0.28, 0.555)]
fender_bot = [(y + 0.0, z - 0.03) for y, z in fender_top]
fender_bot = [(-0.25, 0.53), (-0.30, 0.60), (-0.355, 0.735), (-0.45, 0.82), (-0.575, 0.852), (-1.30, 0.852),
(-1.31, 0.81)]
fpts = fender_top + fender_bot
for sx in (-1, 1):
f = side_profile(f"Fender_{'L' if sx > 0 else 'R'}", fpts, sx * 0.615, 0.25, OD)
bevel(f, 0.006, 1)
# 펜더 안쪽 에이프런
box("Fender_Apron", (0.02, 0.92, 0.36), loc=(sx * 0.49, -0.84, FENDER_TOP - 0.36), material=OD_DARK,
base=True)
# 등화관제등(블랙아웃 램프)
box("Blackout_Lamp", (0.07, 0.05, 0.06), loc=(sx * 0.66, -1.24, FENDER_TOP), material=OD_DARK, base=True)
cylinder("Blackout_Lens", 0.016, 0.006, loc=(sx * 0.66, -1.265, FENDER_TOP + 0.03),
rot=(math.pi / 2, 0, 0), material=LENS, seg=16, base=False)
# ── 차체 욕조(tub) ────────────────────────────────
ARCH_R = 0.44
th0 = math.degrees(math.asin((FLOOR_Z - AXLE_Z) / ARCH_R))
TUB_FRONT, TUB_REAR, TUB_TOP = -0.30, 1.395, 0.98
tub_side = [(TUB_FRONT, FLOOR_Z), (Y_RA - ARCH_R * math.cos(math.radians(th0)), FLOOR_Z)]
tub_side += arc_pts(Y_RA, AXLE_Z, ARCH_R, 180 - th0, th0, 18)[1:-1]
tub_side += [(Y_RA + ARCH_R * math.cos(math.radians(th0)), FLOOR_Z), (TUB_REAR, FLOOR_Z), (TUB_REAR, TUB_TOP),
(0.64, TUB_TOP), (0.58, 0.965), (0.52, 0.92), (0.46, 0.84), (0.39, 0.76), (0.30, 0.71),
(0.18, 0.69), (0.02, 0.69), (-0.09, 0.71), (-0.16, 0.76), (-0.20, 0.84), (-0.225, 0.94),
(-0.24, 1.0), (-0.26, 1.03), (TUB_FRONT, 1.03)]
for sx in (-1, 1):
s = side_profile(f"Body_Side_{'L' if sx > 0 else 'R'}", tub_side, sx * (TUB_HALF - 0.015), 0.03, OD)
bevel(s, 0.008, 2)
# 바깥 모서리를 감싸는 말린 테두리
top_edge = [(sx * TUB_HALF, y, z + 0.004) for y, z in tub_side[tub_side.index((TUB_REAR, TUB_TOP)):]]
curve_tube("Body_Rolled_Edge", top_edge, radius=0.011, material=OD)
box("Body_Floor", (0.94, TUB_REAR - TUB_FRONT, 0.03), loc=(0, (TUB_REAR + TUB_FRONT) / 2, FLOOR_Z),
material=OD_DARK, base=True)
fy1 = Y_RA - ARCH_R * math.cos(math.radians(th0))
for sx in (-1, 1):
box("Body_Floor_Side", (0.23, fy1 - TUB_FRONT, 0.03), loc=(sx * 0.585, (fy1 + TUB_FRONT) / 2, FLOOR_Z),
material=OD_DARK, base=True)
# 뒷바퀴 휠하우스(안쪽 아치 판 + D자 벽)
hw = arc_pts(Y_RA, AXLE_Z, ARCH_R, 180 - th0, th0, 18)
strip = hw + list(reversed(arc_pts(Y_RA, AXLE_Z, ARCH_R + 0.02, 180 - th0, th0, 18)))
side_profile("Wheelhouse_Arch", strip, sx * 0.585, 0.22, OD_DARK)
dwall = arc_pts(Y_RA, AXLE_Z, ARCH_R + 0.02, 180 - th0, th0, 18)
side_profile("Wheelhouse_Wall", dwall, sx * 0.475, 0.02, OD_DARK)
rear = box("Body_Rear_Panel", (2 * TUB_HALF, 0.03, TUB_TOP - FLOOR_Z), loc=(0, TUB_REAR - 0.015, FLOOR_Z),
material=OD, base=True)
bevel(rear, 0.008, 2)
curve_tube("Body_Rolled_Edge_Rear", [(-TUB_HALF, TUB_REAR, TUB_TOP + 0.004), (TUB_HALF, TUB_REAR, TUB_TOP + 0.004)],
radius=0.011, material=OD)
cowl = box("Cowl", (2 * TUB_HALF - 0.03, 0.17, 1.03 - FLOOR_Z), loc=(0, -0.215, FLOOR_Z), material=OD, base=True)
bevel(cowl, 0.012, 2)
# ── 계기판 ─────────────────────────────────────────
for x in (0.10, 0.22, 0.34, -0.05):
cylinder("Gauge", 0.035, 0.012, loc=(x, -0.13, 0.90), rot=(-math.pi / 2, 0, 0), material=CHROME, seg=20)
cylinder("Gauge_Face", 0.028, 0.004, loc=(x, -0.118, 0.90), rot=(-math.pi / 2, 0, 0), material=STEEL, seg=20)
box("Glovebox_Plate", (0.30, 0.008, 0.16), loc=(-0.38, -0.128, 0.82), material=OD_DARK, base=True)
# ── 앞유리 (접이식 프레임) ─────────────────────────
WS_Y, WS_Z, WS_H, WS_W = -0.205, 1.03, 0.66, 1.34
ws = [box("WS_Bottom", (WS_W, 0.05, 0.05), loc=(0, 0, 0), material=OD, base=True),
box("WS_Top", (WS_W, 0.04, 0.045), loc=(0, 0, WS_H - 0.045), material=OD, base=True),
box("WS_Post_L", (0.05, 0.05, WS_H), loc=(WS_W / 2 - 0.025, 0, 0), material=OD, base=True),
box("WS_Post_R", (0.05, 0.05, WS_H), loc=(-WS_W / 2 + 0.025, 0, 0), material=OD, base=True),
box("WS_Center", (0.03, 0.04, WS_H - 0.09), loc=(0, 0, 0.045), material=OD, base=True)]
for o in ws:
bevel(o, 0.006, 1)
frame = bake(join(ws, "Windshield_Frame"))
frame.location = (0, WS_Y, WS_Z)
frame.rotation_euler = (math.radians(-9), 0, 0)
panes = [box("Pane_L", (0.585, 0.008, WS_H - 0.095), loc=(0.31, 0, 0.05), material=GLASS, base=True),
box("Pane_R", (0.585, 0.008, WS_H - 0.095), loc=(-0.31, 0, 0.05), material=GLASS, base=True)]
glass_obj = bake(join(panes, "Windshield_Glass"))
glass_obj.location = (0, WS_Y, WS_Z)
glass_obj.rotation_euler = (math.radians(-9), 0, 0)
wip = []
for x in (0.31, -0.31):
wip.append(box("Wiper_Arm", (0.012, 0.012, 0.32), loc=(x + 0.05, -0.03, WS_H - 0.36), rot=(0, 0.35, 0),
material=STEEL, base=True))
wip.append(cylinder("Wiper_Motor", 0.03, 0.06, loc=(x, -0.02, WS_H - 0.06), rot=(math.pi / 2, 0, 0),
material=STEEL, base=False, seg=12))
wipers = bake(join(wip, "Windshield_Wipers"))
wipers.location = (0, WS_Y, WS_Z)
wipers.rotation_euler = (math.radians(-9), 0, 0)
for sx in (-1, 1): # 앞유리 고정 걸쇠
box("WS_Hinge", (0.05, 0.06, 0.04), loc=(sx * 0.62, WS_Y, WS_Z - 0.01), material=STEEL, base=True)
# ── 운전대 (좌핸들, +X 쪽) ─────────────────────────
DX = 0.33
col_tilt = math.radians(-52)
cylinder("Steering_Column", 0.022, 0.40, loc=(DX, -0.13, 0.80), rot=(col_tilt, 0, 0), material=STEEL, seg=12)
d = Vector((0, -math.sin(col_tilt), math.cos(col_tilt)))
wc = Vector((DX, -0.13, 0.80)) + d * 0.40
torus("Steering_Wheel", 0.19, 0.012, loc=wc, rot=(col_tilt, 0, 0), material=STEEL, seg=40, rseg=8)
for k in range(3):
a = math.radians(90 + 120 * k)
sp = box("Steering_Spoke", (0.19, 0.012, 0.008), material=STEEL)
sp.data.transform(Matrix.Translation((0.095, 0, 0)))
sp.location = wc
m = Euler((col_tilt, 0, 0)).to_matrix() @ Matrix.Rotation(a, 3, 'Z')
sp.rotation_euler = m.to_euler()
# ── 좌석 ───────────────────────────────────────────
for sx in (-1, 1):
x = sx * 0.33
box("Seat_Frame", (0.38, 0.36, 0.10), loc=(x, 0.46, FLOOR_Z + 0.03), material=STEEL, base=True)
c = box("Seat_Cushion", (0.44, 0.42, 0.10), loc=(x, 0.46, FLOOR_Z + 0.13), material=CANVAS, base=True)
bevel(c, 0.03, 3)
b = box("Seat_Back", (0.44, 0.08, 0.42), loc=(x, 0.70, FLOOR_Z + 0.22), rot=(math.radians(-10), 0, 0),
material=CANVAS, base=True)
bevel(b, 0.03, 3)
rc = box("Rear_Seat_Cushion", (0.90, 0.38, 0.10), loc=(0, 1.14, FLOOR_Z + 0.16), material=CANVAS, base=True)
bevel(rc, 0.03, 3)
box("Rear_Seat_Frame", (0.86, 0.34, 0.13), loc=(0, 1.14, FLOOR_Z + 0.03), material=STEEL, base=True)
rb = box("Rear_Seat_Back", (0.90, 0.07, 0.30), loc=(0, 1.33, FLOOR_Z + 0.26), material=CANVAS, base=True)
bevel(rb, 0.025, 3)
# ── 뒤: 예비 타이어, 제리캔, 미등, 견인 고리 ───────
spare = duplicate(wheel, loc=(-0.22, TUB_REAR + 0.105, 0.70), rot=(-math.pi / 2, 0, 0), name="Wheel_Spare")
box("Spare_Bracket", (0.10, 0.04, 0.10), loc=(-0.22, TUB_REAR + 0.01, 0.65), material=STEEL, base=True)
can = box("Jerry_Can", (0.345, 0.165, 0.47), loc=(0.40, TUB_REAR + 0.105, 0.50), material=OD, base=True)
bevel(can, 0.02, 2)
for k in (-1, 0, 1):
box("Jerry_Can_Handle", (0.03, 0.06, 0.03), loc=(0.40 + k * 0.06, TUB_REAR + 0.105, 0.97), material=OD,
base=True)
box("Jerry_Can_Cap", (0.05, 0.05, 0.05), loc=(0.53, TUB_REAR + 0.105, 0.96), material=OD_DARK, base=True)
for sx in (-1, 1):
box("Can_Strap", (0.025, 0.19, 0.03), loc=(0.40 + sx * 0.12, TUB_REAR + 0.105, 0.80), material=STEEL,
base=True)
box("Jerry_Can_Holder", (0.38, 0.03, 0.06), loc=(0.40, TUB_REAR + 0.015, 0.50), material=STEEL, base=True)
for sx in (-1, 1):
box("Tail_Lamp", (0.07, 0.04, 0.10), loc=(sx * 0.63, TUB_REAR + 0.02, 0.80), material=OD_DARK, base=True)
box("Tail_Lens", (0.05, 0.006, 0.035), loc=(sx * 0.63, TUB_REAR + 0.042, 0.84), material=RED, base=True)
box("Bumperette", (0.10, 0.10, 0.12), loc=(sx * 0.58, TUB_REAR + 0.05, 0.40), material=OD, base=True)
torus("Pintle_Hook", 0.05, 0.014, loc=(0.0, TUB_REAR + 0.07, 0.43), rot=(0, math.pi / 2, 0), material=STEEL,
seg=20, rseg=8)
# ── 보닛의 흰 별 + 차량 번호 ───────────────────────
def star(name, R, loc, rot=(0, 0, 0), h=0.003):
pts = []
for k in range(10):
r = R if k % 2 == 0 else R * 0.382
a = math.radians(90 + 36 * k)
pts.append((r * math.cos(a), r * math.sin(a)))
return prism(name, pts, h, loc=loc, rot=rot, material=STAR)
star("Hood_Star", 0.20, (0, -0.78, HOOD_TOP + 0.0005))
for sx in (-1, 1):
t = text3d("Hood_Serial", "USA 20128435", size=0.055, depth=0.001,
loc=(sx * 0.4815, -0.78, 0.945), rot=(math.pi / 2, 0, sx * math.pi / 2), material=STAR)
# ── 왼쪽(운전석) 옆의 삽과 도끼 ─────────────────────
box("Shovel_Handle", (0.03, 0.58, 0.03), loc=(TUB_HALF + 0.02, 0.01, 0.585), material=WOOD, base=False)
box("Shovel_Blade", (0.014, 0.20, 0.17), loc=(TUB_HALF + 0.012, 0.39, 0.59), material=STEEL, base=False)
box("Axe_Handle", (0.03, 0.55, 0.028), loc=(TUB_HALF + 0.02, 0.0, 0.645), material=WOOD, base=False)
box("Axe_Head", (0.024, 0.08, 0.15), loc=(TUB_HALF + 0.02, -0.30, 0.645), material=STEEL, base=False)
for y in (-0.15, 0.25):
box("Tool_Clamp", (0.04, 0.03, 0.11), loc=(TUB_HALF + 0.015, y, 0.61), material=STEEL, base=False)
# ── 손잡이 ─────────────────────────────────────────
for sx in (-1, 1):
x = sx * (TUB_HALF + 0.03)
curve_tube("Grab_Handle", [(sx * TUB_HALF, 1.02, 0.88), (x, 1.04, 0.885), (x, 1.22, 0.885),
(sx * TUB_HALF, 1.24, 0.88)], radius=0.011, material=OD)
# ── 엔진·배기·변속 레버·백미러 ─────────────────────
eng = box("Engine_Block", (0.46, 0.78, 0.36), loc=(0, -0.80, 0.50), material=STEEL, base=True)
bevel(eng, 0.03, 2)
box("Oil_Pan", (0.34, 0.50, 0.08), loc=(0, -0.88, 0.43), material=STEEL, base=True)
cylinder("Exhaust_Pipe", 0.022, 1.55, loc=(-0.30, 0.55, 0.42), rot=(math.pi / 2, 0, 0), material=STEEL,
base=False, seg=12)
cylinder("Muffler", 0.075, 0.45, loc=(-0.30, 0.30, 0.42), rot=(math.pi / 2, 0, 0), material=STEEL,
base=False, seg=20)
for k, (dx, ang) in enumerate(((0.06, -0.25), (0.11, -0.05), (0.15, 0.15))):
lv = cylinder("Shift_Lever", 0.009, 0.38 - k * 0.06, loc=(dx, 0.02, FLOOR_Z + 0.03), rot=(ang, 0, 0),
material=STEEL, seg=8)
sphere("Shift_Knob", 0.022, loc=(dx, 0.02 - math.sin(ang) * (0.38 - k * 0.06),
FLOOR_Z + 0.03 + math.cos(ang) * (0.38 - k * 0.06)),
material=OD_DARK, seg=12, rings=8)
cylinder("Hand_Brake", 0.01, 0.20, loc=(0.0, -0.13, 0.80), rot=(-math.pi / 2, 0, 0), material=STEEL, seg=8)
mir = bake(join([cylinder("Mirror_Arm", 0.007, 0.16, loc=(0, 0, 0), rot=(0, math.radians(60), 0),
material=STEEL, seg=8),
cylinder("Mirror_Head", 0.055, 0.015, loc=(0.135, 0, 0.08), rot=(math.pi / 2, 0, 0),
material=STEEL, base=False, seg=20)], "Rearview_Mirror"))
mir.location = (WS_W / 2, WS_Y + 0.03, WS_Z + 0.45)
mir.rotation_euler = (math.radians(-9), 0, 0)
for sx in (-1, 1): # 앞유리를 눕혔을 때 받치는 고무 받침
cylinder("Windshield_Rest", 0.025, 0.02, loc=(sx * 0.30, -0.95, HOOD_TOP), material=RUBBER, seg=16)
# 보닛 위 걸쇠
box("Hood_Clamp", (0.04, 0.05, 0.012), loc=(sx * 0.40, -1.24, HOOD_TOP), material=STEEL, base=True)
# ── 접어 내린 캔버스 지붕과 지붕 활대 ───────────────
roll = cylinder("Canvas_Top_Folded", 0.065, 1.30, loc=(0, 1.27, TUB_TOP + 0.066), rot=(0, math.pi / 2, 0),
material=CANVAS, base=False, seg=24)
smooth(roll)
for k, (yb, zb) in enumerate(((1.06, 0.0), (1.12, 0.03))):
pts = [(-TUB_HALF - 0.012, yb, TUB_TOP - 0.06)]
pts += [(-TUB_HALF - 0.012, yb + 0.05, TUB_TOP + 0.02 + zb), (-TUB_HALF - 0.012, 1.42 + 0.03 * k, TUB_TOP + 0.03 + zb),
(TUB_HALF + 0.012, 1.42 + 0.03 * k, TUB_TOP + 0.03 + zb), (TUB_HALF + 0.012, yb + 0.05, TUB_TOP + 0.02 + zb),
(TUB_HALF + 0.012, yb, TUB_TOP - 0.06)]
curve_tube(f"Top_Bow_{k + 1}", pts, radius=0.011, material=STEEL)
for sx in (-1, 1):
box("Top_Bow_Bracket", (0.02, 0.10, 0.08), loc=(sx * (TUB_HALF + 0.008), 1.09, TUB_TOP - 0.10),
material=STEEL, base=True)

다른 모형