Загрузка данных
"""
Радиатор видеокарты (только радиатор, без вентиляторов и корпуса).
Плотный пакет тонких рёбер + никелированные тепловые трубки + медное основание.
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))