
끌어서 회전, 휠·두 손가락으로 확대
이 모형에 대하여
윌리스 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 mathfrom 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.36AXLE_Z = TIRE_RY_FA, Y_RA = -1.10, 0.93 # 앞/뒤 차축(휠베이스 2.03m)TRACK = 0.6225 # 윤거 1.245m 의 절반TUB_HALF = 0.72FLOOR_Z = 0.50GRILLE_Y = -1.31HOOD_TOP = 1.05FENDER_TOP = 0.88def 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.50gpts = [(-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_botfor 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.44th0 = math.degrees(math.asin((FLOOR_Z - AXLE_Z) / ARCH_R))TUB_FRONT, TUB_REAR, TUB_TOP = -0.30, 1.395, 0.98tub_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.34ws = [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.33col_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.40torus("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)






