Загрузка данных
"""
Разъём питания 12VHPWR (16-pin, PCIe CEM 5.0 / 12V-2x6), две части:
1) гнездо на плате видеокарты (корпус с защёлкой, 12 силовых + 4 сигнальных штыря);
2) кабельный штекер (корпус с 16 полостями, терминалы, защёлка, 12 силовых + 4 сигнальных провода, оплётка).
Размеры близки к реальным (шаг 3.0 мм, корпус штекера ~20.4 мм), мелкие детали упрощены.
Запуск: Blender -> Scripting -> Создать -> вставить код -> Текст -> Запустить скрипт.
Единицы: 1 единица Blender = 1 мм. Ось вставки - Z (штекер входит сверху вниз).
"""
import bpy
import bmesh
import mathutils
from math import pi
# ---------- Настройки ----------
MATED = False # True: штекер вставлен в гнездо; False: приподнят
GAP = 28.0 # на сколько штекер приподнят, если MATED = False, мм
WIRE_LEN = 55.0 # длина показанных проводов, мм
PITCH = 3.0 # шаг силовых контактов, мм
ROW_B = (-2.2, 0.8) # положение двух силовых рядов по оси Y, мм
SENSE_B = 3.1 # ряд сигнальных контактов по оси Y, мм
SENSE_A = (-4.5, -1.5, 1.5, 4.5)
PREFIX = "HPWR_"
# ---------- Материалы ----------
def make_material(name, c0, c1, metallic, rough, rough_var=0.1, noise_scale=6.0,
stretch=(1, 1, 1), bump=0.1, detail=5.0, traces=None):
mat = bpy.data.materials.get(name) or bpy.data.materials.new(name)
try:
mat.use_nodes = True
except Exception:
pass
nt = mat.node_tree
nt.nodes.clear()
out = nt.nodes.new("ShaderNodeOutputMaterial")
bsdf = nt.nodes.new("ShaderNodeBsdfPrincipled")
nt.links.new(bsdf.outputs["BSDF"], out.inputs["Surface"])
bsdf.inputs["Base Color"].default_value = c1
bsdf.inputs["Metallic"].default_value = metallic
bsdf.inputs["Roughness"].default_value = rough
try:
tc = nt.nodes.new("ShaderNodeTexCoord")
mp = nt.nodes.new("ShaderNodeMapping")
nt.links.new(tc.outputs["Object"], mp.inputs["Vector"])
mp.inputs["Scale"].default_value = tuple(noise_scale * s for s in stretch)
nz = nt.nodes.new("ShaderNodeTexNoise")
nz.inputs["Scale"].default_value = 1.0
nz.inputs["Detail"].default_value = detail
nt.links.new(mp.outputs["Vector"], nz.inputs["Vector"])
ramp = nt.nodes.new("ShaderNodeValToRGB")
ramp.color_ramp.elements[0].color = c0
ramp.color_ramp.elements[1].color = c1
nt.links.new(nz.outputs["Fac"], ramp.inputs["Fac"])
color_src = ramp.outputs["Color"]
if traces is not None:
mp2 = nt.nodes.new("ShaderNodeMapping")
nt.links.new(tc.outputs["Object"], mp2.inputs["Vector"])
mp2.inputs["Scale"].default_value = (0.5, 0.5, 0.5)
vn = nt.nodes.new("ShaderNodeTexVoronoi")
vn.feature = "DISTANCE_TO_EDGE"
nt.links.new(mp2.outputs["Vector"], vn.inputs["Vector"])
tr = nt.nodes.new("ShaderNodeValToRGB")
tr.color_ramp.interpolation = "CONSTANT"
tr.color_ramp.elements[0].position = 0.0
tr.color_ramp.elements[0].color = traces[0]
e = tr.color_ramp.elements.new(0.04)
e.color = traces[1]
nt.links.new(vn.outputs["Distance"], tr.inputs["Fac"])
color_src = tr.outputs["Color"]
nt.links.new(color_src, bsdf.inputs["Base Color"])
mr = nt.nodes.new("ShaderNodeMapRange")
mr.inputs["From Min"].default_value = 0.3
mr.inputs["From Max"].default_value = 0.7
mr.inputs["To Min"].default_value = max(rough - rough_var, 0.02)
mr.inputs["To Max"].default_value = min(rough + rough_var, 1.0)
nt.links.new(nz.outputs["Fac"], mr.inputs["Value"])
nt.links.new(mr.outputs["Result"], bsdf.inputs["Roughness"])
bp = nt.nodes.new("ShaderNodeBump")
bp.inputs["Strength"].default_value = bump
bp.inputs["Distance"].default_value = 0.05
nt.links.new(nz.outputs["Fac"], bp.inputs["Height"])
nt.links.new(bp.outputs["Normal"], bsdf.inputs["Normal"])
except Exception as ex:
print("Текстуры материала", name, "не применены:", ex)
return mat
def build_materials():
m = {}
m["plastic"] = make_material(PREFIX + "Plastic", (0.010, 0.010, 0.012, 1), (0.040, 0.040, 0.046, 1),
0.0, 0.5, 0.15, noise_scale=40.0, bump=0.35, detail=3.0)
m["latch"] = make_material(PREFIX + "Latch", (0.03, 0.03, 0.035, 1), (0.09, 0.09, 0.10, 1),
0.0, 0.4, 0.1, noise_scale=30.0, bump=0.2, detail=3.0)
m["gold"] = make_material(PREFIX + "Gold", (0.80, 0.55, 0.15, 1), (1.0, 0.80, 0.30, 1),
1.0, 0.25, 0.1, noise_scale=20.0, bump=0.05)
m["tin"] = make_material(PREFIX + "Tin", (0.55, 0.57, 0.60, 1), (0.80, 0.82, 0.85, 1),
1.0, 0.3, 0.1, noise_scale=15.0, bump=0.05)
m["yellow"] = make_material(PREFIX + "WireYellow", (0.75, 0.55, 0.0, 1), (1.0, 0.78, 0.02, 1),
0.0, 0.35, 0.08, noise_scale=30.0, bump=0.05, detail=2.0)
m["black"] = make_material(PREFIX + "WireBlack", (0.004, 0.004, 0.005, 1), (0.02, 0.02, 0.022, 1),
0.0, 0.4, 0.08, noise_scale=30.0, bump=0.05, detail=2.0)
m["sense"] = make_material(PREFIX + "WireSense", (0.25, 0.25, 0.27, 1), (0.55, 0.56, 0.60, 1),
0.0, 0.4, 0.08, noise_scale=30.0, bump=0.05, detail=2.0)
m["sleeve"] = make_material(PREFIX + "Sleeve", (0.006, 0.006, 0.007, 1), (0.05, 0.05, 0.055, 1),
0.0, 0.8, 0.1, noise_scale=90.0, stretch=(1.0, 1.0, 0.15), bump=0.6, detail=1.0)
m["pcb"] = make_material(PREFIX + "PCB", (0.004, 0.004, 0.006, 1), (0.02, 0.02, 0.024, 1),
0.0, 0.4, 0.1, noise_scale=30.0, bump=0.15,
traces=((0.30, 0.18, 0.07, 1), (0.012, 0.012, 0.015, 1)))
m["text"] = make_material(PREFIX + "Silk", (0.7, 0.7, 0.7, 1), (0.95, 0.95, 0.95, 1),
0.0, 0.5, 0.05, noise_scale=30.0, bump=0.02, detail=2.0)
return m
def assign(obj, mat):
obj.data.materials.clear()
obj.data.materials.append(mat)
# ---------- Построитель сетки ----------
class MB:
def __init__(self):
self.v = []
self.f = []
def _loop_prism(self, a, b):
n = len(a)
base = len(self.v)
self.v += a + b
self.f.append(tuple(base + i for i in reversed(range(n))))
self.f.append(tuple(base + n + i for i in range(n)))
for i in range(n):
j = (i + 1) % n
self.f.append((base + i, base + j, base + n + j, base + n + i))
def poly(self, ab, c0, c1):
"""Призма вдоль Z по контуру (x, y)."""
self._loop_prism([(a, b, c0) for a, b in ab], [(a, b, c1) for a, b in ab])
def tube(self, outer, inner, c0, c1):
n = len(outer)
base = len(self.v)
self.v += [(a, b, c0) for a, b in outer] + [(a, b, c1) for a, b in outer]
self.v += [(a, b, c0) for a, b in inner] + [(a, b, c1) for a, b in inner]
for i in range(n):
j = (i + 1) % n
self.f.append((base + i, base + j, base + n + j, base + n + i))
self.f.append((base + 2 * n + i, base + 3 * n + i, base + 3 * n + j, base + 2 * n + j))
self.f.append((base + i, base + 2 * n + i, base + 2 * n + j, base + j))
self.f.append((base + n + i, base + n + j, base + 3 * n + j, base + 3 * n + i))
def box(self, a0, a1, b0, b1, c0, c1):
pts = [(a0, b0, c0), (a1, b0, c0), (a1, b1, c0), (a0, b1, c0),
(a0, b0, c1), (a1, b0, c1), (a1, b1, c1), (a0, b1, c1)]
base = len(self.v)
self.v += pts
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(base + i for i in q))
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 rect(a0, a1, b0, b1):
return [(a0, b0), (a1, b0), (a1, b1), (a0, b1)]
def circle(r, a0, b0, n=14):
from math import sin, cos
return [(a0 + r * cos(2 * pi * k / n), b0 + r * sin(2 * pi * k / n)) for k in range(n)]
def boolean_cut(obj, cutter_mb, name):
cutter = cutter_mb.build(PREFIX + name)
try:
bpy.context.view_layer.update()
mod = obj.modifiers.new("Cut", "BOOLEAN")
mod.operation = "DIFFERENCE"
mod.solver = "EXACT"
mod.object = cutter
dg = bpy.context.evaluated_depsgraph_get()
new_mesh = bpy.data.meshes.new_from_object(obj.evaluated_get(dg))
obj.modifiers.remove(mod)
old = obj.data
obj.data = new_mesh
bpy.data.meshes.remove(old)
except Exception as ex:
print("Булева операция не удалась (", name, "):", ex)
cmesh = cutter.data
bpy.data.objects.remove(cutter, do_unlink=True)
bpy.data.meshes.remove(cmesh)
def power_positions():
"""(a, b, ряд) для 12 силовых контактов."""
pts = []
for r, b in enumerate(ROW_B):
for i in range(6):
pts.append(((i - 2.5) * PITCH, b, r))
return pts
# ---------- Гнездо на плате ----------
def build_header(mats):
# плата
pcb = MB()
pcb.box(-26.0, 26.0, -18.0, 18.0, -1.6, 0.0)
pcb.build(PREFIX + "PCB", mats["pcb"])
# корпус гнезда: основание + стенки с окном под защёлку
ia, ib = 10.45, 5.2 # внутренние полуразмеры
oa, ob = 11.5, 6.2 # внешние полуразмеры
H, floor = 9.5, 1.2
sh = MB()
sh.box(-oa, oa, -ob, ob, 0.0, floor) # основание
sh.box(-oa, oa, -ob, -ib, 0.0, H) # стенка -Y
sh.box(-oa, -ia, -ob, ob, 0.0, H) # стенка -X
sh.box(ia, oa, -ob, ob, 0.0, H) # стенка +X
sh.box(-ia, ia, ib, ob, 0.0, 3.5) # стенка +Y: низ
sh.box(-ia, ia, ib, ob, 7.5, H) # стенка +Y: верх
sh.box(-ia, -5.0, ib, ob, 3.5, 7.5) # стенка +Y: левая стойка окна
sh.box(5.0, ia, ib, ob, 3.5, 7.5) # стенка +Y: правая стойка окна
sh.box(-ia, -ia + 1.15, -ib, -ib + 0.9, floor, H) # ключ-выступ в углу
sh.build(PREFIX + "Header_Shroud", mats["plastic"])
# штыри
pw, ps = MB(), MB()
for a, b, _ in power_positions():
pw.box(a - 0.7, a + 0.7, b - 0.7, b + 0.7, floor, 7.8)
for a in SENSE_A:
ps.box(a - 0.32, a + 0.32, SENSE_B - 0.32, SENSE_B + 0.32, floor, 6.0)
pw.build(PREFIX + "Header_PowerPins", mats["gold"])
ps.build(PREFIX + "Header_SensePins", mats["gold"])
# паяные контакты снизу платы
sol = MB()
for a, b, _ in power_positions():
sol.box(a - 0.9, a + 0.9, b - 0.9, b + 0.9, -2.4, -1.6)
for a in SENSE_A:
sol.box(a - 0.5, a + 0.5, SENSE_B - 0.5, SENSE_B + 0.5, -2.1, -1.6)
sol.build(PREFIX + "Header_SolderJoints", mats["tin"])
# маркировка на плате
for body, loc, size in (("12VHPWR", (0.0, -10.5, 0.02), 2.6), ("12V-2x6", (0.0, 10.5, 0.02), 2.0)):
cu = bpy.data.curves.new(PREFIX + "Silk_" + body, "FONT")
cu.body = body
cu.size = size
cu.align_x = "CENTER"
cu.align_y = "CENTER"
cu.extrude = 0.01
ob_ = bpy.data.objects.new(PREFIX + "Silk_" + body, cu)
bpy.context.collection.objects.link(ob_)
ob_.location = loc
ob_.data.materials.append(mats["text"])
# ---------- Кабельный штекер ----------
def build_plug(mats):
c0 = 1.2 + (0.0 if MATED else GAP) # низ штекера (сторона контактов)
H = 12.0
outer = [(-8.2, -4.9), (10.2, -4.9), (10.2, 4.9), (-10.2, 4.9), (-10.2, -2.9)] # со срезанным углом
body = MB()
body.poly(outer, c0, c0 + H)
housing = body.build(PREFIX + "Plug_Housing")
cut = MB()
for a, b, _ in power_positions():
cut.box(a - 1.1, a + 1.1, b - 1.1, b + 1.1, c0 - 1.0, c0 + 8.0)
for a in SENSE_A:
cut.box(a - 0.6, a + 0.6, SENSE_B - 0.6, SENSE_B + 0.6, c0 - 1.0, c0 + 6.0)
boolean_cut(housing, cut, "PlugCutter")
assign(housing, mats["plastic"])
# терминалы внутри полостей
term = MB()
for a, b, _ in power_positions():
term.tube(rect(a - 0.95, a + 0.95, b - 0.95, b + 0.95), rect(a - 0.6, a + 0.6, b - 0.6, b + 0.6),
c0 + 2.5, c0 + 8.0)
for a in SENSE_A:
term.tube(rect(a - 0.5, a + 0.5, SENSE_B - 0.5, SENSE_B + 0.5),
rect(a - 0.3, a + 0.3, SENSE_B - 0.3, SENSE_B + 0.3), c0 + 2.0, c0 + 6.0)
term.build(PREFIX + "Plug_Terminals", mats["gold"])
# защёлка: рычаг вдоль стенки +Y и крючок, входящий в окно гнезда
lat = MB()
lat.box(-4.5, 4.5, 4.9, 5.15, c0 + 3.5, c0 + H - 0.5) # рычаг
lat.box(-4.5, 4.5, 4.9, 5.9, c0 + 3.5, c0 + 5.0) # крючок
lat.box(-4.5, 4.5, 4.9, 6.4, c0 + H - 3.0, c0 + H - 0.5) # нажимная площадка
lat.build(PREFIX + "Plug_Latch", mats["latch"])
# провода: 6 жёлтых (+12 В), 6 чёрных (земля), 4 тонких сигнальных
top = c0 + H
wy, wb, ws = MB(), MB(), MB()
for a, b, r in power_positions():
(wy if r == 1 else wb).poly(circle(1.05, a, b, 14), top - 0.5, top + WIRE_LEN)
for a in SENSE_A:
ws.poly(circle(0.4, a, SENSE_B, 10), top - 0.5, top + WIRE_LEN)
wy.build(PREFIX + "Wires_12V", mats["yellow"])
wb.build(PREFIX + "Wires_GND", mats["black"])
ws.build(PREFIX + "Wires_Sense", mats["sense"])
# оплётка и переходник
sl = MB()
sl.tube(rect(-9.9, 9.9, -4.6, 4.6), rect(-9.4, 9.4, -4.1, 4.1), top, top + WIRE_LEN * 0.55)
sl.build(PREFIX + "Sleeve", mats["sleeve"])
boot = MB()
boot.tube(rect(-10.0, 10.0, -4.7, 4.7), rect(-9.4, 9.4, -4.1, 4.1), top, top + 5.0)
boot.build(PREFIX + "Plug_Boot", mats["latch"])
def build_scene():
cam_data = bpy.data.cameras.new(PREFIX + "Camera")
cam_data.lens = 70
cam_data.clip_end = 2000.0
cam = bpy.data.objects.new(PREFIX + "Camera", cam_data)
bpy.context.collection.objects.link(cam)
cam.location = (60.0, -85.0, 55.0)
target = mathutils.Vector((0.0, 0.0, 14.0 if MATED else 24.0))
cam.rotation_euler = (target - cam.location).to_track_quat("-Z", "Y").to_euler()
bpy.context.scene.camera = cam
for name, loc, rot, energy, size in (
("Key", (50.0, -60.0, 90.0), (pi / 3.2, 0.0, 0.5), 9000, 60),
("Fill", (-60.0, -40.0, 40.0), (pi / 2.6, 0.0, -0.9), 4000, 50)):
ld = bpy.data.lights.new(PREFIX + name, "AREA")
ld.energy = energy
ld.size = size
lo = bpy.data.objects.new(PREFIX + name, ld)
bpy.context.collection.objects.link(lo)
lo.location = loc
lo.rotation_euler = rot
try:
for area in bpy.context.screen.areas:
if area.type == "VIEW_3D":
area.spaces[0].shading.type = "MATERIAL"
except Exception as ex:
print("Не удалось переключить вид:", ex)
def main():
for ob in list(bpy.data.objects):
if ob.name.startswith(PREFIX):
bpy.data.objects.remove(ob, do_unlink=True)
mats = build_materials()
build_header(mats)
build_plug(mats)
build_scene()
print("Готово: разъём 12VHPWR")
main()