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


"""
Зона 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)