import bpy import bmesh import mathutils import random import math class SolidTreeGenerator: def __init__(self, levels=5, length=3.0, radius=0.3): self.levels = levels self.base_length = length self.base_radius = radius # Хранилище для данных: (start_pos, end_pos, radius_start, radius_end) self.branches_data = [] def calculate_tree(self, start_pos, direction, length, radius, level): if level <= 0 or length < 0.1: return end_pos = start_pos + direction * length # Сохраняем данные сегмента self.branches_data.append({ 'start': start_pos.copy(), 'end': end_pos.copy(), 'r_start': radius, 'r_end': radius * 0.7 }) # 1. Основной ствол (продолжение) trunk_dir = (direction + self.get_random_vector(0.1)).normalized() self.calculate_tree(end_pos, trunk_dir, length * 0.8, radius * 0.7, level - 1) # 2. Боковые ветки (ветвление) if level > 1: num_sides = random.randint(2, 3) # Минимум 2 ветки для видимости for _ in range(num_sides): # Создаем вектор, сильно отклоненный от ствола (30-60 градусов) axis = self.get_random_vector(1.0).normalized() angle = math.radians(random.uniform(30, 60)) side_dir = direction.copy() side_dir.rotate(mathutils.Quaternion(axis, angle)) # Боковые ветки короче self.calculate_tree(end_pos, side_dir, length * 0.6, radius * 0.5, level - 1) def get_random_vector(self, intensity): return mathutils.Vector(( random.uniform(-intensity, intensity), random.uniform(-intensity, intensity), random.uniform(-intensity, intensity) )) def build_mesh(self): mesh = bpy.data.meshes.new("TreeMesh") obj = bpy.data.objects.new("Tree", mesh) bpy.context.collection.objects.link(obj) bm = bmesh.new() skin_layer = bm.verts.layers.skin.verify() # Словарь для предотвращения дублирования вершин в одной точке # Ключ - кортеж координат, Значение - объект вершины BMesh vert_map = {} for b in self.branches_data: # Превращаем координаты в кортежи для словаря s_key = tuple(round(v, 4) for v in b['start']) e_key = tuple(round(v, 4) for v in b['end']) # Получаем или создаем начальную вершину if s_key not in vert_map: v_start = bm.verts.new(b['start']) v_start[skin_layer].radius = (b['r_start'], b['r_start']) vert_map[s_key] = v_start else: v_start = vert_map[s_key] # Получаем или создаем конечную вершину if e_key not in vert_map: v_end = bm.verts.new(b['end']) v_end[skin_layer].radius = (b['r_end'], b['r_end']) vert_map[e_key] = v_end else: v_end = vert_map[e_key] # Создаем ребро, если его еще нет if not bm.edges.get((v_start, v_end)): bm.edges.new((v_start, v_end)) # Находим корень (самую нижнюю точку) и помечаем его root_v = min(bm.verts, key=lambda v: v.co.z) root_v[skin_layer].use_root = True bm.to_mesh(mesh) bm.free() # Модификаторы obj.modifiers.new(name="Skin", type='SKIN') sub = obj.modifiers.new(name="Subdiv", type='SUBSURF') sub.levels = 1 # Для начала 1, чтобы не тормозило # Очистка сцены bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete() # Запуск generator = SolidTreeGenerator(levels=5, length=3.0, radius=0.4) generator.calculate_tree(mathutils.Vector((0,0,0)), mathutils.Vector((0,0,1)), 3.0, 0.4, 5) generator.build_mesh()