Загрузка данных
"""
Задняя планка видеокарты: 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()