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


import bpy
import random
import math

# ============================================================
# ОЧИСТКА СЦЕНЫ
# ============================================================
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

for coll in (bpy.data.meshes, bpy.data.materials, bpy.data.lights,
             bpy.data.cameras, bpy.data.curves, bpy.data.objects):
    for block in list(coll):
        if block.users == 0:
            coll.remove(block)

# ============================================================
# МАТЕРИАЛЫ — поиск Principled BSDF по типу узла
# ============================================================
def make_mat(name, color, roughness=0.85, metallic=0.0, emission=None):
    mat = bpy.data.materials.new(name=name)
    mat.use_nodes = True
    tree = mat.node_tree

    bsdf = None
    for node in tree.nodes:
        if node.type == 'BSDF_PRINCIPLED':
            bsdf = node
            break

    if bsdf is None:
        bsdf = tree.nodes.new('ShaderNodeBsdfPrincipled')
        out = None
        for n in tree.nodes:
            if n.type == 'OUTPUT_MATERIAL':
                out = n
                break
        if out is None:
            out = tree.nodes.new('ShaderNodeOutputMaterial')
        tree.links.new(bsdf.outputs['BSDF'], out.inputs['Surface'])

    def set_input(sock_name, value):
        if sock_name in bsdf.inputs:
            bsdf.inputs[sock_name].default_value = value

    set_input("Base Color", (*color, 1.0))
    set_input("Roughness", roughness)
    set_input("Metallic", metallic)
    if emission is not None:
        set_input("Emission Color", (*emission, 1.0))
        set_input("Emission Strength", 2.0)

    return mat

MAT = {
    "wood_dark":  make_mat("WoodDark",  (0.10, 0.05, 0.02)),
    "wood_light": make_mat("WoodLight", (0.30, 0.17, 0.07)),
    "roof":       make_mat("Roof",      (0.28, 0.10, 0.05)),
    "roof_old":   make_mat("RoofOld",   (0.16, 0.13, 0.09)),
    "ground":     make_mat("Ground",    (0.18, 0.14, 0.07)),
    "stone":      make_mat("Stone",     (0.32, 0.30, 0.28)),
    "leaves":     make_mat("Leaves",    (0.08, 0.22, 0.05)),
    "trunk":      make_mat("Trunk",     (0.13, 0.07, 0.03)),
    "water":      make_mat("Water",     (0.05, 0.15, 0.25), roughness=0.05),
    "dirt":       make_mat("Dirt",      (0.22, 0.14, 0.07)),
    "lamp":       make_mat("Lamp",      (1.0, 0.75, 0.4),
                           emission=(1.0, 0.6, 0.2)),
}

def assign(obj, mat):
    obj.data.materials.append(mat)
    return obj

# ============================================================
# ЗЕМЛЯ
# ============================================================
bpy.ops.mesh.primitive_plane_add(size=80, location=(0, 0, 0))
ground = bpy.context.object
ground.name = "Ground"
assign(ground, MAT["ground"])

# ============================================================
# ДОМ — с правильной двускатной крышей (угол 40°)
# ============================================================
def create_house(loc, rot_z=0.0, size=(3.0, 3.5, 2.4), roof_mat=None):
    if roof_mat is None:
        roof_mat = MAT["roof"]
    x, y, z = loc
    w, d, h = size

    # --- Родитель ---
    bpy.ops.object.empty_add(type='PLAIN_AXES', location=(x, y, z))
    root = bpy.context.object
    root.name = "House_Root"
    root.rotation_euler = (0, 0, rot_z)
    parts = []

    # --- Корпус ---
    bpy.ops.mesh.primitive_cube_add(size=1, location=(0, 0, h / 2))
    body = bpy.context.object
    body.name = "House_Body"
    body.scale = (w, d, h)
    assign(body, MAT["wood_light"])
    parts.append(body)

    # --- Двускатная крыша (правильный угол 40°) ---
    angle = math.radians(40)
    overhang = 0.35
    half_w = w / 2 + overhang
    roof_h = math.tan(angle) * half_w
    slope_len = half_w / math.cos(angle)

    for side in (-1, 1):
        bpy.ops.mesh.primitive_cube_add(size=1)
        slope = bpy.context.object
        slope.name = "Roof_Slope"
        slope.location = (
            side * half_w / 2,
            0,
            h + roof_h / 2
        )
        slope.scale = (slope_len, d + 0.6, 0.10)
        slope.rotation_euler = (0, side * angle, 0)
        assign(slope, roof_mat)
        parts.append(slope)

    # --- Фронтоны (треугольники на торцах) ---
    for side in (-1, 1):
        mesh = bpy.data.meshes.new(f"Gable_{side}")
        verts = [(-w / 2, 0, 0), (w / 2, 0, 0), (0, 0, roof_h)]
        faces = [(0, 1, 2)]
        mesh.from_pydata(verts, [], faces)
        mesh.update()
        gable = bpy.data.objects.new(f"Gable_{side}", mesh)
        bpy.context.collection.objects.link(gable)
        gable.location = (0, side * d / 2, h)
        assign(gable, MAT["wood_light"])
        parts.append(gable)

    # --- Дверь (передняя стена +Y) ---
    bpy.ops.mesh.primitive_cube_add(size=1)
    door = bpy.context.object
    door.name = "Door"
    door.scale = (0.7, 0.06, 1.3)
    door.location = (0, d / 2 + 0.03, 0.65)
    assign(door, MAT["wood_dark"])
    parts.append(door)

    # --- Окна (боковые стены ±X) ---
    for side in (-1, 1):
        bpy.ops.mesh.primitive_cube_add(size=1)
        win = bpy.context.object
        win.name = "Window"
        win.scale = (0.06, 0.5, 0.5)
        win.location = (side * (w / 2 + 0.03), 0, h * 0.6)
        assign(win, MAT["wood_dark"])
        parts.append(win)

    # --- Труба ---
    bpy.ops.mesh.primitive_cube_add(size=1)
    chimney = bpy.context.object
    chimney.name = "Chimney"
    chimney.scale = (0.35, 0.35, 1.4)
    chimney.location = (-w * 0.25, 0, h + roof_h * 0.75)
    assign(chimney, MAT["stone"])
    parts.append(chimney)

    # --- Привязка ---
    for p in parts:
        p.parent = root

    return root

# ============================================================
# ДЕРЕВО
# ============================================================
def create_tree(loc, height=4.0, seed=None):
    if seed is not None:
        random.seed(seed)
    x, y, z = loc

    bpy.ops.mesh.primitive_cylinder_add(
        vertices=8, radius=0.18, depth=height,
        location=(x, y, z + height / 2))
    trunk = bpy.context.object
    trunk.name = "Trunk"
    assign(trunk, MAT["trunk"])

    for _ in range(4):
        ox = random.uniform(-0.55, 0.55)
        oy = random.uniform(-0.55, 0.55)
        oz = height + random.uniform(-0.4, 0.7)
        r = random.uniform(0.85, 1.25)
        bpy.ops.mesh.primitive_uv_sphere_add(
            segments=12, ring_count=8, radius=r,
            location=(x + ox, y + oy, z + oz))
        leaves = bpy.context.object
        leaves.name = "Leaves"
        assign(leaves, MAT["leaves"])

# ============================================================
# КОЛОДЕЦ
# ============================================================
def create_well(loc):
    x, y, z = loc

    bpy.ops.mesh.primitive_cylinder_add(vertices=24, radius=0.85, depth=0.9,
                                         location=(x, y, z + 0.45))
    ring = bpy.context.object
    ring.name = "Well_Ring"
    assign(ring, MAT["stone"])

    bpy.ops.mesh.primitive_cylinder_add(vertices=24, radius=0.7, depth=0.05,
                                         location=(x, y, z + 0.85))
    water = bpy.context.object
    water.name = "Well_Water"
    assign(water, MAT["water"])

    for dx in (-0.7, 0.7):
        bpy.ops.mesh.primitive_cube_add(size=1, location=(x + dx, y, z + 1.5))
        post = bpy.context.object
        post.scale = (0.16, 0.16, 1.5)
        assign(post, MAT["wood_dark"])

    bpy.ops.mesh.primitive_cone_add(
        vertices=4, radius1=1.15, depth=0.7,
        location=(x, y, z + 2.35),
        rotation=(0, 0, math.radians(45)))
    roof = bpy.context.object
    roof.name = "Well_Roof"
    assign(roof, MAT["roof"])

# ============================================================
# ЗАБОР
# ============================================================
def create_fence(start, end, posts=12, height=1.0):
    x1, y1 = start
    x2, y2 = end
    dx = x2 - x1
    dy = y2 - y1
    total_len = math.hypot(dx, dy)
    angle = math.atan2(dy, dx)

    for i in range(posts):
        t = i / (posts - 1)
        x = x1 + dx * t
        y = y1 + dy * t
        bpy.ops.mesh.primitive_cube_add(size=1, location=(x, y, height / 2))
        post = bpy.context.object
        post.name = "Fence_Post"
        post.scale = (0.12, 0.12, height)
        assign(post, MAT["wood_dark"])

        if i < posts - 1:
            t2 = (i + 0.5) / (posts - 1)
            mx = x1 + dx * t2
            my = y1 + dy * t2
            seg_len = total_len / (posts - 1)
            for zz in (height * 0.35, height * 0.75):
                bpy.ops.mesh.primitive_cube_add(size=1, location=(mx, my, zz))
                rail = bpy.context.object
                rail.name = "Fence_Rail"
                rail.scale = (seg_len, 0.07, 0.07)
                rail.rotation_euler = (0, 0, angle)
                assign(rail, MAT["wood_dark"])

# ============================================================
# ФОНАРЬ
# ============================================================
def create_lamp(loc, height=2.6):
    x, y, z = loc
    bpy.ops.mesh.primitive_cylinder_add(vertices=8, radius=0.07,
                                         depth=height,
                                         location=(x, y, z + height / 2))
    pole = bpy.context.object
    pole.name = "Lamp_Pole"
    assign(pole, MAT["wood_dark"])

    bpy.ops.mesh.primitive_uv_sphere_add(radius=0.18,
                                          location=(x, y, z + height + 0.1))
    bulb = bpy.context.object
    bulb.name = "Lamp_Bulb"
    assign(bulb, MAT["lamp"])

    bpy.ops.object.light_add(type='POINT', location=(x, y, z + height + 0.1))
    light = bpy.context.object
    light.name = "Lamp_Light"
    light.data.energy = 40
    light.data.color = (1.0, 0.7, 0.35)
    light.data.shadow_soft_size = 0.4

# ============================================================
# РАССТАНОВКА
# ============================================================

# Дома
create_house(loc=(-6, 4, 0), rot_z=math.radians(15), size=(3.2, 3.6, 2.5))
create_house(loc=(5.5, -4, 0), rot_z=math.radians(-35),
             size=(2.7, 3.1, 2.1), roof_mat=MAT["roof_old"])
create_house(loc=(-5, -8, 0), rot_z=math.radians(60), size=(2.9, 3.3, 2.3))
create_house(loc=(7, 4.5, 0), rot_z=math.radians(200),
             size=(2.5, 3.0, 2.0), roof_mat=MAT["roof_old"])

# Деревья
tree_coords = [(-11, -3), (9, 7), (-9, -10), (11, -8), (0, 9),
               (-2, -13), (8, 9), (-13, 6), (13, 1)]
for i, pos in enumerate(tree_coords):
    create_tree(pos, height=random.uniform(3.5, 5.2), seed=i)

# Колодец
create_well((0, 0, 0))

# Фонари
create_lamp((-3, 3))
create_lamp((3, -3))
create_lamp((6, 6))

# Забор по периметру
create_fence((-13, -12), (13, -12), posts=16)
create_fence((13, -12), (13, 12), posts=14)
create_fence((-13, -12), (-13, 12), posts=14)
create_fence((-13, 12), (13, 12), posts=16)

# Дороги
bpy.ops.mesh.primitive_plane_add(size=1, location=(0, 0, 0.02))
road = bpy.context.object
road.name = "Road"
road.scale = (3.2, 28, 1)
road.rotation_euler = (0, 0, math.radians(12))
assign(road, MAT["dirt"])

bpy.ops.mesh.primitive_plane_add(size=1, location=(0, 0, 0.02))
road2 = bpy.context.object
road2.name = "Road2"
road2.scale = (2.6, 20, 1)
road2.rotation_euler = (0, 0, math.radians(95))
assign(road2, MAT["dirt"])

# Камни
random.seed(42)
for _ in range(30):
    x = random.uniform(-12, 12)
    y = random.uniform(-12, 12)
    if math.hypot(x, y) < 2.5:
        continue
    if abs(x) < 2 or abs(y) < 2:
        continue
    bpy.ops.mesh.primitive_ico_sphere_add(
        subdivisions=1, radius=random.uniform(0.15, 0.35),
        location=(x, y, 0.08))
    stone = bpy.context.object
    stone.name = "Stone"
    stone.scale = (1, 1, 0.5)
    stone.rotation_euler = (random.uniform(0, math.pi),
                            random.uniform(0, math.pi),
                            random.uniform(0, math.pi))
    assign(stone, MAT["stone"])

# ============================================================
# ОСВЕЩЕНИЕ
# ============================================================
bpy.ops.object.light_add(type='SUN', location=(12, -14, 16))
sun = bpy.context.object
sun.name = "Sun"
sun.data.energy = 3.0
sun.data.color = (1.0, 0.72, 0.45)
sun.data.angle = math.radians(3)
sun.rotation_euler = (math.radians(55), 0, math.radians(50))

bpy.ops.object.light_add(type='AREA', location=(-10, 10, 8))
fill = bpy.context.object
fill.name = "Fill"
fill.data.energy = 300
fill.data.size = 12
fill.data.color = (0.55, 0.7, 1.0)

# ============================================================
# МИР
# ============================================================
world = bpy.data.worlds.get("World")
if world is None:
    world = bpy.data.worlds.new("World")
    bpy.context.scene.world = world
world.use_nodes = True

bg = None
for n in world.node_tree.nodes:
    if n.type == 'BACKGROUND':
        bg = n
        break
if bg:
    bg.inputs["Color"].default_value = (0.35, 0.45, 0.6, 1.0)
    bg.inputs["Strength"].default_value = 0.85

# ============================================================
# КАМЕРА
# ============================================================
bpy.ops.object.camera_add(location=(20, -20, 11))
camera = bpy.context.object
camera.name = "Camera"
camera.rotation_euler = (math.radians(63), 0, math.radians(45))
camera.data.lens = 42
bpy.context.scene.camera = camera

# ============================================================
# РЕНДЕР
# ============================================================
scene = bpy.context.scene
scene.render.engine = 'CYCLES'
scene.cycles.samples = 128
scene.cycles.use_denoising = True
scene.cycles.device = 'CPU'

scene.render.resolution_x = 1920
scene.render.resolution_y = 1080

scene.view_settings.view_transform = 'AgX'
try:
    scene.view_settings.look = 'AgX - Punchy'
except Exception:
    pass

print("✅ Готово! Старая деревня создана в Blender 4.5.4")
print("   F12 — рендер, Numpad 0 — вид из камеры.")