Загрузка данных


"""
Задняя планка видеокарты: 3 x DisplayPort + 1 x HDMI (ГНЁЗДА, а не штекеры).
Запуск: Blender -> Scripting -> Создать -> вставить код -> Текст -> Запустить скрипт.
Единицы: 1 единица Blender = 1 мм. Гнёзда смотрят в сторону -Y (вид спереди: Numpad 1).
После запуска наведи курсор на 3D-вид и нажми Home.
"""
import bpy
import bmesh
from math import pi

# ---------- Настройки ----------
PORTS = ["DP", "DP", "DP", "HDMI"]   # порядок снизу вверх, можно менять
PITCH_Z = 21.0       # расстояние между центрами портов, мм
FIRST_Z = 12.0       # высота центра первого порта, мм
PLATE_W = 20.32      # ширина планки (один слот), мм
PLATE_Z0, PLATE_Z1 = -6.0, 90.0   # нижний/верхний край планки, мм
PLATE_T = 1.2        # толщина планки, мм
WALL = 0.3           # толщина металлической рамки гнезда, мм
CLEAR = 0.15         # зазор для штекера, мм
DEPTH = 12.0         # глубина гнезда, мм

PREFIX = "GPU_"


# ---------- Материалы ----------
def make_material(name, color, metallic=0.0, roughness=0.5):
    mat = bpy.data.materials.get(name) or bpy.data.materials.new(name)
    try:
        mat.use_nodes = True
    except Exception:
        pass
    nodes = mat.node_tree.nodes
    # ищем узел по типу (имя зависит от языка интерфейса и версии Blender)
    bsdf = next((n for n in nodes if n.type == "BSDF_PRINCIPLED"), None)
    if bsdf is None:
        bsdf = nodes.new("ShaderNodeBsdfPrincipled")
        out = next((n for n in nodes if n.type == "OUTPUT_MATERIAL"), None)
        if out is None:
            out = nodes.new("ShaderNodeOutputMaterial")
        mat.node_tree.links.new(bsdf.outputs["BSDF"], out.inputs["Surface"])
    bsdf.inputs["Base Color"].default_value = color
    bsdf.inputs["Metallic"].default_value = metallic
    bsdf.inputs["Roughness"].default_value = roughness
    return mat


MAT_METAL = make_material("GPU_Metal", (0.75, 0.77, 0.80, 1), 1.0, 0.3)
MAT_PLASTIC = make_material("GPU_Plastic", (0.02, 0.02, 0.02, 1), 0.0, 0.45)
MAT_GOLD = make_material("GPU_Gold", (1.0, 0.77, 0.25, 1), 1.0, 0.2)


def assign(obj, mat):
    obj.data.materials.clear()
    obj.data.materials.append(mat)


# ---------- Профили в координатах (u, v): u - длинная сторона, v - короткая ----------
def hdmi_uv(w, h, c=1.0):
    return [(-w / 2, h / 2), (w / 2, h / 2), (w / 2, 0.0),
            (w / 2 - c, -h / 2), (-w / 2 + c, -h / 2), (-w / 2, 0.0)]


def dp_uv(w, h, c=1.5):
    return [(-w / 2, -h / 2), (w / 2, -h / 2), (w / 2, h / 2 - c),
            (w / 2 - c, h / 2), (-w / 2, h / 2)]


# Размеры штекера: (ширина, высота, срез, контактов сверху, контактов снизу)
SPEC = {
    "HDMI": dict(w=13.9, h=4.45, c=1.0, prof=hdmi_uv, top=10, bot=9),
    "DP": dict(w=16.1, h=4.76, c=1.5, prof=dp_uv, top=10, bot=10),
}


# ---------- Построитель сетки ----------
class MB:
    def __init__(self):
        self.v = []
        self.f = []

    def box(self, x0, x1, y0, y1, z0, z1):
        b = len(self.v)
        self.v += [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0),
                   (x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1)]
        for q in ((0, 3, 2, 1), (4, 5, 6, 7), (0, 1, 5, 4),
                  (2, 3, 7, 6), (1, 2, 6, 5), (3, 0, 4, 7)):
            self.f.append(tuple(b + i for i in q))

    def prism(self, uv, zc, y0, y1):
        """Призма вдоль оси Y; u -> Z (вверх), v -> X."""
        n = len(uv)
        b = len(self.v)
        self.v += [(v, y0, zc + u) for (u, v) in uv]
        self.v += [(v, y1, zc + u) for (u, v) in uv]
        self.f.append(tuple(b + i for i in reversed(range(n))))
        self.f.append(tuple(b + n + i for i in range(n)))
        for i in range(n):
            j = (i + 1) % n
            self.f.append((b + i, b + j, b + n + j, b + n + i))

    def tube(self, outer_uv, inner_uv, zc, y0, y1):
        n = len(outer_uv)
        b = len(self.v)
        self.v += [(v, y0, zc + u) for (u, v) in outer_uv]
        self.v += [(v, y1, zc + u) for (u, v) in outer_uv]
        self.v += [(v, y0, zc + u) for (u, v) in inner_uv]
        self.v += [(v, y1, zc + u) for (u, v) in inner_uv]
        for i in range(n):
            j = (i + 1) % n
            self.f.append((b + i, b + j, b + n + j, b + n + i))
            self.f.append((b + 2 * n + i, b + 3 * n + i, b + 3 * n + j, b + 2 * n + j))
            self.f.append((b + i, b + 2 * n + i, b + 2 * n + j, b + j))
            self.f.append((b + n + i, b + n + j, b + 3 * n + j, b + 3 * n + i))

    def build(self, name, mat=None):
        mesh = bpy.data.meshes.new(name)
        mesh.from_pydata(self.v, [], self.f)
        mesh.update()
        obj = bpy.data.objects.new(name, mesh)
        bpy.context.collection.objects.link(obj)
        bm = bmesh.new()
        bm.from_mesh(mesh)
        bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
        bm.to_mesh(mesh)
        bm.free()
        if mat is not None:
            assign(obj, mat)
        return obj


# ---------- Размеры гнезда ----------
def port_profiles(kind):
    s = SPEC[kind]
    wi, hi = s["w"] + CLEAR, s["h"] + CLEAR
    inner = s["prof"](wi, hi, s["c"])
    outer = s["prof"](wi + 2 * WALL, hi + 2 * WALL, s["c"])
    return s, wi, hi, inner, outer


# ---------- Сборка ----------
def main():
    for ob in list(bpy.data.objects):
        if ob.name.startswith(PREFIX):
            bpy.data.objects.remove(ob, do_unlink=True)

    centers = [FIRST_Z + i * PITCH_Z for i in range(len(PORTS))]

    # 1) Планка с вырезами (булева операция, затем применяем результат)
    plate_mb = MB()
    plate_mb.box(-PLATE_W / 2, PLATE_W / 2, 0.0, PLATE_T, PLATE_Z0, PLATE_Z1)
    plate = plate_mb.build(PREFIX + "Bracket")

    cut_mb = MB()
    for kind, zc in zip(PORTS, centers):
        _, _, _, _, outer = port_profiles(kind)
        cut_mb.prism(outer, zc, -1.0, PLATE_T + 1.0)
    cutter = cut_mb.build(PREFIX + "Cutter")

    bpy.context.view_layer.update()
    mod = plate.modifiers.new("Holes", "BOOLEAN")
    mod.operation = "DIFFERENCE"
    mod.solver = "EXACT"
    mod.object = cutter
    dg = bpy.context.evaluated_depsgraph_get()
    new_mesh = bpy.data.meshes.new_from_object(plate.evaluated_get(dg))
    plate.modifiers.remove(mod)
    old = plate.data
    plate.data = new_mesh
    bpy.data.meshes.remove(old)
    assign(plate, MAT_METAL)

    cut_mesh = cutter.data
    bpy.data.objects.remove(cutter, do_unlink=True)
    bpy.data.meshes.remove(cut_mesh)

    # 2) Металлические рамки гнёзд
    shell_mb = MB()
    wall_mb = MB()      # пластиковая задняя стенка
    tongue_mb = MB()    # язычок
    pins_mb = MB()      # контакты
    tt, ct = 1.0, 0.12  # толщина язычка и контактов
    tip = 2.5           # где начинается язычок (от переднего края)

    for kind, zc in zip(PORTS, centers):
        s, wi, hi, inner, outer = port_profiles(kind)
        shell_mb.tube(outer, inner, zc, 0.0, DEPTH + 1.0)
        wall_mb.prism(inner, zc, DEPTH, DEPTH + 1.0)

        tw = wi - 2.0
        tongue_mb.box(-tt / 2, tt / 2, tip, DEPTH, zc - tw / 2, zc + tw / 2)

        for count, sign in ((s["top"], 1), (s["bot"], -1)):
            x = sign * (tt / 2 + ct / 2)
            for i in range(count):
                u = zc + (i - (count - 1) / 2) * 1.0
                pins_mb.box(x - ct / 2, x + ct / 2, tip + 0.5, DEPTH - 0.5,
                            u - 0.25, u + 0.25)

    shell_mb.build(PREFIX + "Shells", MAT_METAL)
    wall_mb.build(PREFIX + "BackWalls", MAT_PLASTIC)
    tongue_mb.build(PREFIX + "Tongues", MAT_PLASTIC)
    pins_mb.build(PREFIX + "Pins", MAT_GOLD)

    # 3) Свет
    bpy.ops.object.light_add(type="AREA", location=(0, -80, 60))
    light = bpy.context.active_object
    light.name = PREFIX + "Light"
    light.data.energy = 10000
    light.data.size = 60
    light.rotation_euler = (pi / 2.4, 0, 0)

    print("Готово: планка видеокарты с портами", PORTS)


main()