Загрузка данных
"""
Зона GPU-чипа с обратной стороны платы (как на фото):
плата, H-образный прижимной кронштейн с 4 винтами, окно под чип,
в окне - блок из 8 конденсаторных массивов и плотная сетка SMD-конденсаторов.
Blender 3.6 - 4.x. Запуск: Scripting -> Open -> Run Script. Единицы: метры.
"""
import bpy
import bmesh
from math import pi, sin, cos, hypot, radians
from mathutils import Matrix
# ======================= ПАРАМЕТРЫ =======================
CAP_PITCH = 0.0028 # шаг сетки конденсаторов (меньше - гуще)
BRACKET_T = 0.0035 # толщина кронштейна
VIEW = 'TOP' # 'TOP' - вид сверху (как на фото), 'ANGLE' - перспектива
RENDER_SAMPLES = 128
RES = 1600
# ======================= ОЧИСТКА =======================
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
for blk in (bpy.data.meshes, bpy.data.materials, bpy.data.curves):
for item in list(blk):
blk.remove(item)
scn = bpy.context.scene
col = scn.collection
def link(obj):
col.objects.link(obj)
return obj
def find_node(nodes, ntype):
for nd in nodes:
if nd.type == ntype:
return nd
return None
def mat(name, color, rough, metallic=0.0):
m = bpy.data.materials.new(name)
m.use_nodes = True
b = find_node(m.node_tree.nodes, 'BSDF_PRINCIPLED')
b.inputs["Base Color"].default_value = color
b.inputs["Roughness"].default_value = rough
b.inputs["Metallic"].default_value = metallic
return m
M_PCB = mat("PCB", (0.012, 0.012, 0.014, 1), 0.55)
M_BRACKET = mat("Bracket", (0.040, 0.040, 0.046, 1), 0.45, 0.85)
M_SCREW = mat("Screw", (0.82, 0.82, 0.85, 1), 0.15, 1.0)
M_BODY = mat("CapBody", (0.05, 0.03, 0.02, 1), 0.4)
M_TERM = mat("CapTerminal", (0.78, 0.60, 0.30, 1), 0.28, 1.0)
M_GOLD = mat("GoldFrame", (0.88, 0.68, 0.32, 1), 0.25, 1.0)
M_BLACK = mat("BlackInset", (0.008, 0.008, 0.009, 1), 0.35)
M_LIGHT = mat("LightInset", (0.72, 0.52, 0.30, 1), 0.35, 0.8)
M_RING = mat("PocketRing", (0.03, 0.03, 0.035, 1), 0.4, 0.5)
# ======================= ХЕЛПЕРЫ =======================
def rounded_pts(hx, hy, r, n=10):
pts = []
for sx, sy, a0 in [(1, 1, 0), (-1, 1, 90), (-1, -1, 180), (1, -1, 270)]:
for i in range(n + 1):
a = radians(a0 + 90 * i / n)
pts.append((sx * (hx - r) + r * cos(a), sy * (hy - r) + r * sin(a)))
return pts
def extrude_poly(name, pts, thick, z0, material):
bm = bmesh.new()
vs = [bm.verts.new((x, y, z0)) for x, y in pts]
face = bm.faces.new(vs)
res = bmesh.ops.extrude_face_region(bm, geom=[face])
nv = [e for e in res['geom'] if isinstance(e, bmesh.types.BMVert)]
bmesh.ops.translate(bm, verts=nv, vec=(0, 0, thick))
bmesh.ops.recalc_face_normals(bm, faces=bm.faces[:])
me = bpy.data.meshes.new(name)
bm.to_mesh(me)
bm.free()
ob = link(bpy.data.objects.new(name, me))
ob.data.materials.append(material)
return ob
def box(bm, cx, cy, cz, sx, sy, sz, rot=0.0):
m = Matrix.Translation((cx, cy, cz)) @ Matrix.Rotation(rot, 4, 'Z') \
@ Matrix.Diagonal((sx, sy, sz, 1.0))
bmesh.ops.create_cube(bm, size=1.0, matrix=m)
def mesh_obj(name, bm, material):
me = bpy.data.meshes.new(name)
bm.to_mesh(me)
bm.free()
ob = link(bpy.data.objects.new(name, me))
ob.data.materials.append(material)
return ob
# ======================= ПЛАТА =======================
pcb = extrude_poly("PCB", rounded_pts(0.075, 0.075, 0.006), 0.0016, -0.0016, M_PCB)
# ======================= КРОНШТЕЙН (H-образный, с 4 "ушами") =======================
S = 0.05 / 375.0 # метров на пиксель эталонного фото
R = [(0, -257), (245, -257), (255, -267), (255, -407), (340, -407),
(340, -177), (290, -127), (290, 123), (320, 173), (320, 408),
(225, 408), (225, 313), (200, 273), (0, 273)]
L = [(-x, y) for x, y in reversed(R)][1:-1]
outline = [(x * S, -y * S) for x, y in R + L]
bracket = extrude_poly("Bracket", outline, BRACKET_T, 0.0, M_BRACKET)
# окно под чип (вырез)
cutter = extrude_poly("WindowCutter", rounded_pts(0.0265, 0.0265, 0.007),
0.02, -0.005, M_BRACKET)
cutter.display_type = 'WIRE'
cutter.hide_render = True
bo = bracket.modifiers.new("Window", 'BOOLEAN')
bo.operation = 'DIFFERENCE'
bo.object = cutter
bo.solver = 'EXACT'
bv = bracket.modifiers.new("Bevel", 'BEVEL')
bv.width = 0.0003
bv.segments = 2
bv.limit_method = 'ANGLE'
# ======================= ВИНТЫ =======================
screw_xy = [(sx * 295 * S, -sy * S)
for sx in (-1, 1) for sy in (-372, 358)]
bm_s = bmesh.new()
bm_c = bmesh.new()
for (x, y) in screw_xy:
bmesh.ops.create_cone(bm_s, cap_ends=True, segments=32, radius1=0.0033,
radius2=0.0033, depth=0.0014,
matrix=Matrix.Translation((x, y, BRACKET_T + 0.0007)))
box(bm_c, x, y, BRACKET_T + 0.0015, 0.0030, 0.0005, 0.0004)
box(bm_c, x, y, BRACKET_T + 0.0015, 0.0030, 0.0005, 0.0004, pi / 2)
screws = mesh_obj("Screws", bm_s, M_SCREW)
for p in screws.data.polygons:
p.use_smooth = True
mesh_obj("ScrewSlots", bm_c, M_BLACK)
# ======================= ОКНО: кольцо, массивы, конденсаторы =======================
# кольцо вокруг зоны чипа
cu = bpy.data.curves.new("PocketRing", 'CURVE')
cu.dimensions = '2D'
cu.fill_mode = 'BOTH'
cu.extrude = 0.0004
for pts in (rounded_pts(0.0252, 0.0252, 0.0068), rounded_pts(0.0242, 0.0242, 0.0062)):
sp = cu.splines.new('POLY')
sp.points.add(len(pts) - 1)
for p, (x, y) in zip(sp.points, pts):
p.co = (x, y, 0, 1)
sp.use_cyclic_u = True
ring = link(bpy.data.objects.new("PocketRing", cu))
ring.location = (0, 0, 0.0004)
ring.data.materials.append(M_RING)
# центральный блок: 2 колонки x 4 ряда
bm_gold = bmesh.new()
bm_dark = bmesh.new()
bm_light = bmesh.new()
for cx in (-0.0070, 0.0070):
for i in range(4):
cy = (-1.5 + i) * 0.0092
box(bm_gold, cx, cy, 0.0003, 0.0085, 0.0075, 0.0006)
tgt = bm_light if i in (0, 3) else bm_dark
box(tgt, cx, cy, 0.0007, 0.0066, 0.0056, 0.0003)
mesh_obj("CoreFrames", bm_gold, M_GOLD)
mesh_obj("CoreInsetDark", bm_dark, M_BLACK)
mesh_obj("CoreInsetLight", bm_light, M_LIGHT)
# сетка SMD-конденсаторов вокруг блока
CL, CW, CH, CT = 0.0016, 0.0008, 0.0007, 0.00032
HALF, RAD = 0.0235, 0.006
bm_body = bmesh.new()
bm_term = bmesh.new()
n = int(HALF / CAP_PITCH) + 1
count = 0
for i in range(-n, n + 1):
for j in range(-n, n + 1):
x, y = i * CAP_PITCH, j * CAP_PITCH
ax, ay = abs(x), abs(y)
if ax > HALF or ay > HALF:
continue
c0 = HALF - RAD
if ax > c0 and ay > c0 and hypot(ax - c0, ay - c0) > RAD:
continue
if ax < 0.0132 and ay < 0.0215:
continue
rot = 0.0 if ax > ay else pi / 2 # ориентация "по радиусу"
box(bm_body, x, y, CH / 2, CL, CW, CH, rot)
for sg in (-1, 1):
o = sg * (CL / 2 - CT / 2)
box(bm_term, x + o * cos(rot), y + o * sin(rot), CH / 2,
CT, CW * 1.02, CH * 1.02, rot)
count += 1
mesh_obj("CapBodies", bm_body, M_BODY)
mesh_obj("CapTerminals", bm_term, M_TERM)
# ======================= СЦЕНА =======================
world = scn.world or bpy.data.worlds.new("World")
scn.world = world
world.use_nodes = True
bg = find_node(world.node_tree.nodes, 'BACKGROUND')
bg.inputs["Color"].default_value = (1, 1, 1, 1)
bg.inputs["Strength"].default_value = 1.0
def area(name, loc, energy, size):
ld = bpy.data.lights.new(name, 'AREA')
ld.energy = energy
ld.size = size
ob = link(bpy.data.objects.new(name, ld))
ob.location = loc
return ob
area("Key", (0.0, -0.08, 0.30), 25, 0.25)
area("Fill", (-0.20, 0.10, 0.18), 12, 0.20)
area("Rim", (0.20, 0.10, 0.12), 12, 0.15)
cam_d = bpy.data.cameras.new("Cam")
cam = link(bpy.data.objects.new("Cam", cam_d))
if VIEW == 'TOP':
cam_d.type = 'ORTHO'
cam_d.ortho_scale = 0.16
cam.location = (0, 0, 0.5)
else:
cam_d.type = 'PERSP'
cam_d.lens = 70
cam.location = (0.10, -0.16, 0.14)
tgt = link(bpy.data.objects.new("Target", None))
tgt.location = (0, 0, 0)
tr = cam.constraints.new('TRACK_TO')
tr.target = tgt
tr.track_axis = 'TRACK_NEGATIVE_Z'
tr.up_axis = 'UP_Y'
scn.camera = cam
scn.render.engine = 'CYCLES'
scn.cycles.samples = RENDER_SAMPLES
scn.cycles.use_denoising = True
scn.render.resolution_x = scn.render.resolution_y = RES
scn.view_settings.view_transform = 'Standard'
print("Готово: зона чипа создана. Конденсаторов в сетке: %d" % count)