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


"""
Радиатор видеокарты (только радиатор, без вентиляторов и корпуса).
Плотный пакет тонких рёбер + никелированные тепловые трубки + медное основание.
Blender 3.6 - 4.x. Запуск: Scripting -> Open -> Run Script.
Единицы: метры (размеры близки к RTX 5090 Astral).
"""
import bpy
import bmesh
from math import pi, radians

# ======================= ПАРАМЕТРЫ =======================
LENGTH = 0.358          # длина радиатора (вдоль карты, ось X)
WIDTH = 0.135           # ширина (ось Y)
HEIGHT = 0.060          # высота пакета рёбер (ось Z)
FIN_PITCH = 0.0016      # шаг рёбер (меньше - гуще)
FIN_T = 0.0004          # толщина ребра
SEGMENTS = 3            # на сколько секций разделён пакет
GAP = 0.004             # зазор между секциями
PIPE_COUNT = 6          # число тепловых трубок
PIPE_R = 0.004          # радиус трубки (диаметр 8 мм)
BASE_LEN = 0.11         # длина медного основания
BASE_X = -0.10          # положение основания по X
FIN_DARK = False        # True - тёмно-серые рёбра (анодирование)
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):
    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_metal(name, color, rough, metallic=1.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


fin_col = (0.16, 0.16, 0.17, 1) if FIN_DARK else (0.80, 0.81, 0.83, 1)
M_FIN = mat_metal("Aluminium", fin_col, 0.32)
M_PIPE = mat_metal("NickelPipe", (0.66, 0.63, 0.60, 1), 0.20)
M_COPPER = mat_metal("Copper", (0.85, 0.42, 0.24, 1), 0.25)
M_PLATE = mat_metal("EndPlate", (0.05, 0.05, 0.055, 1), 0.45, 0.8)

# ======================= ТЕПЛОВЫЕ ТРУБКИ =======================
# вдоль X, в шахматном порядке по высоте
pipes = []
step = (WIDTH - 0.03) / (PIPE_COUNT - 1)
for i in range(PIPE_COUNT):
    y = -(WIDTH - 0.03) / 2 + i * step
    z = PIPE_R if i % 2 == 0 else PIPE_R + 0.011
    bpy.ops.mesh.primitive_cylinder_add(radius=PIPE_R, depth=LENGTH + 0.006,
                                        vertices=40, location=(0, y, z),
                                        rotation=(0, pi / 2, 0))
    p = bpy.context.active_object
    p.name = "HeatPipe%d" % i
    p.data.materials.append(M_PIPE)
    bpy.ops.object.shade_smooth()
    pipes.append(p)

# ======================= РЕБРО С ОТВЕРСТИЯМИ =======================
bm = bmesh.new()
bmesh.ops.create_cube(bm, size=1.0)
bmesh.ops.scale(bm, vec=(FIN_T, WIDTH, HEIGHT), verts=bm.verts)
bmesh.ops.translate(bm, vec=(0, 0, HEIGHT / 2), verts=bm.verts)
fin = bpy.data.objects.new("FinCutter", bpy.data.meshes.new("FinMesh"))
bm.to_mesh(fin.data)
bm.free()
link(fin)
for p in pipes:
    mod = fin.modifiers.new("Hole", 'BOOLEAN')
    mod.operation = 'DIFFERENCE'
    mod.object = p
    mod.solver = 'EXACT'
bpy.context.view_layer.update()
dg = bpy.context.evaluated_depsgraph_get()
fin_mesh = bpy.data.meshes.new_from_object(fin.evaluated_get(dg))
fin_mesh.materials.clear()
fin_mesh.materials.append(M_FIN)
bpy.data.objects.remove(fin)          # заготовка больше не нужна

# ======================= СЕКЦИИ ПАКЕТА РЁБЕР =======================
seg_len = (LENGTH - GAP * (SEGMENTS - 1)) / SEGMENTS
count = int(seg_len / FIN_PITCH)
for s in range(SEGMENTS):
    ob = link(bpy.data.objects.new("FinStack%d" % s, fin_mesh))
    ob.location = (-LENGTH / 2 + s * (seg_len + GAP), 0, 0)
    arr = ob.modifiers.new("Array", 'ARRAY')
    arr.count = count
    arr.use_relative_offset = False
    arr.use_constant_offset = True
    arr.constant_offset_displace = (FIN_PITCH, 0, 0)

# ======================= ТОРЦЕВЫЕ ПЛАСТИНЫ =======================
for sx in (-1, 1):
    bpy.ops.mesh.primitive_cube_add(size=1, location=(sx * (LENGTH / 2 + 0.001), 0, HEIGHT / 2))
    pl = bpy.context.active_object
    pl.name = "EndPlate"
    pl.scale = (0.002, WIDTH + 0.004, HEIGHT + 0.002)
    pl.data.materials.append(M_PLATE)

# ======================= МЕДНОЕ ОСНОВАНИЕ =======================
bpy.ops.mesh.primitive_cube_add(size=1, location=(BASE_X, 0, -0.003))
base = bpy.context.active_object
base.name = "VaporBase"
base.scale = (BASE_LEN, WIDTH, 0.006)
base.data.materials.append(M_COPPER)
bev = base.modifiers.new("Bevel", 'BEVEL')
bev.width = 0.0008
bev.segments = 2

# ======================= СЦЕНА (студия) =======================
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.1, -0.3, 0.7), 120, 0.6)
area("Fill", (-0.5, 0.2, 0.4), 60, 0.5)
area("Back", (0.3, 0.5, 0.3), 80, 0.4)

target = link(bpy.data.objects.new("Target", None))
target.location = (0, 0, HEIGHT / 2)
cam_d = bpy.data.cameras.new("Cam")
cam_d.lens = 85
cam = link(bpy.data.objects.new("Cam", cam_d))
cam.location = (0.38, -0.55, 0.30)
tr = cam.constraints.new('TRACK_TO')
tr.target = target
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 = RES
scn.render.resolution_y = int(RES * 0.7)
scn.view_settings.view_transform = 'Standard'

print("Готово: радиатор создан. Рёбер: %d" % (count * SEGMENTS))