 rely on EEVEE-only effects for the final appearance. All materials must show correctly in Cycles. At this stage we are using Blender workbench, so materials don't render their colors. But we can see the geometry looks amazing!
Oh look at that structure! It's absolutely perfect. The corrugated iron sliding off, the gaps in the planks, the timber beams, etc.
Wait, let's see why the roof is placed like that:
The roof is placed partially in the center, but also some parts are offset or tilted.
Wait! Let's check how the roof is rotated and translated relative to the walls.
Ah, in `assemble_and_place_house`, we wrote:
```python
    # Rotate first
    for obj in [walls, roof]:
        if obj:
            obj.rotation_euler = (0, 0, rot_z)
            obj.location = pos
```
Wait! The roof sheets were created at their global coordinates during creation, but with local-like math around (0,0). So when we assign `obj.location = pos`, it moves them from (0,0) to `pos`.
But wait! When the sheets are created, they are added to the scene at coordinates like `(local_x, local_y, local_z)`. So their pivot point is at (0,0,0), and their vertex coordinates are already at `(local_x, local_y, local_z)`.
So when we set `obj.rotation_euler = (0, 0, rot_z)` and `obj.location = pos`, both the walls and the roof are transformed perfectly, because both of them are built around (0,0,0) as their origin!
Wait, in the screenshot, why is some roof segment lying on the ground next to the house?
Ah! Look at the right of the house: there's a sheet of corrugated iron lying on the ground!
Oh! That is brilliant! That sheet slid off completely and landed on the ground, or it shows major decay. That is incredibly realistic! But wait, let's check if the roof is aligned correctly over the walls.
In the screenshot, the roof and the walls are located in the same place (around the house center).
Wait! Let's examine the screenshot carefully.
Ah! The roof's top peak is open because some panels are missing.
And yes! Some sheets have slid down and are hanging off the wall, and one sheet has slid all the way to the ground. That's incredibly cool!
Let's see if there are support beams underneath. Yes, we can see the skeleton of the rafter beams!
This is extremely beautiful!

Wait, let's look at the location of the house.
The scene should contain an abandoned *village* (plural: houses), reclaimed by nature.
So let's place about 3 houses of different sizes and conditions:
- House 1: Close to the camera, partially ruined. (Let's say size 5m x 3.5m, rotated, highly decayed).
- House 2: A bit further back, slightly larger or smaller (size 6m x 4.0m), rotated differently, maybe lying on a hill.
- House 3: Further back, a shell of a house (only some walls left, no roof, completely overgrown).
- We can also add some broken fences, stone walls, or ruined barns.
Let's first write a python script to generate multiple houses. Let's make sure the heights are adjusted so they sit nicely on the terrain!
Wait! How do we know the height of the terrain at a specific (X, Y) coordinate?
We can calculate it using the same formula we used to deform the terrain!
Remember our terrain Z formula:
```python
def get_terrain_height(x, y):
    # Large hills
    z = 1.0 * math.sin(x * 0.1) * math.cos(y * 0.1)
    # Small ridges
    z += 0.4 * math.sin(x * 0.3) * math.sin(y * 0.4)
    # Random undulating micro-details
    z += 0.1 * math.cos(x * 0.8 + y * 0.5)

    # Pathway/riverbed depression
    path_dist = abs(x + y * 0.5) / 10.0
    if path_dist < 1.0:
        z -= (1.0 - path_dist) * 0.6
    return z
```
This is brilliant! We can query the height of the terrain at any (X, Y) coordinate and place the house exactly on the ground!
Let's write a python function to place the house. Since the house has custom width and length, we can sample the terrain height at the center of the house, and set the house's Z coordinate to that height. This is incredibly elegant and ensures that the houses sit perfectly on the ground!

Wait, let's think about **vegetation and ivy creeping up the walls**.
We want ivy to crawl up the walls and through broken windows.
How can we generate beautiful vines?
We can create a vine generator:
- It starts on the ground near the house wood planks, i.e. around the perimeter of the house.
- For each vine, we can start at some random ground position near the house wall, say:
  `start_x = house_center_x + offset_x`, `start_y = house_center_y + offset_y`.
- It grows upwards by adding vertical cylindrical segments or lines.
- It "clings" to the wall. For example, if it's on the front wall (Y = -width/2), Y stays close to the wall (with a little bit of noise), and Z grows from 0 to 3.0. X can snake left and right using a sine wave or random walk.
- This is super easy and looks absolutely amazing!
- Let's add leaves:
  At each node of the vine, we can add 1 or 2 small green leaf planes (e.g. size 0.15m), slightly rotated randomly so they look like flat, overlapping ivy leaves!
  By doing this in Python and combining all the vine trunks and leaves into a single mesh, we can create multiple extremely realistic looking creeping ivies on the walls and roof of the houses in just a fraction of a second!
This is an outstanding idea. Let's design the vine/leaf generator mathematically.

Let's write a python function `create_ivy_on_wall(name, wall_plane, start_pos, end_height, side_dir)`:
Let's generalize it:
We can generate a vine path in 3D:
- Let's say we have an Ivy plant. It starts at `(x_start, y_start, z_start)`.
- It has e.g., 3-5 branches.
- For each branch, we can generate a list of points.
- As it grows, the next point is:
  `p_next = p_current + Vector((dx, dy, dz))`
  where `dz` is positive (growth upward, e.g. 0.1m to 0.2m), and `dx, dy` are small random values.
- To make it "cling" to the target, we can clamp or attract the points to the nearest wall surface of the house, OR we can generate the vine relative to the house's local coord system, which makes the math trivial!
  Yes! In local coordinates of the house:
  - Inside a house of size `(L, W, H)`, the walls are at `X = -L/2`, `X = L/2`, `Y = -W/2`, `Y = W/2`.
  - So if we want to grow a vine on the front wall (Y = -W/2):
    Local X coordinates snake around some starting X.
    Local Y is constant at `-W/2 + random_epsilon`.
    Local Z grows from 0 up to `H + peak_height`. When Z exceeds `H`, the vine can start growing along the sloped roof!
    Along the roof slope, the local Y coordinate moves towards 0 (the peak of the roof), and local Z moves towards `peak_h`!
    This is incredibly simple and elegant! The math is 100% robust and produces perfect wall-and-roof-climbing ivy that hugs the house contours perfectly.
Let's write down this beautiful algorithm:
- Local coordinates of house:
- Growth stage 1: Climbing up the wall at X_start, Y = side * W/2.
  Z goes from 0 to H.
  At each step, we move Z up by 0.15m. X has some random walk (e.g. `X_next = X_curr + random(-0.08, 0.08)`). Y has a tiny noise to make it rough and organic.
- Growth stage 2: Once Z reaches H, it transitions to the roof slope!
  For slope side (Y < 0, Y moves towards 0; Z moves towards `peak_h`):
  Let's say at each step, we increment the distance along the slope.
  So Y increases (moves towards 0), and Z increases towards `peak_h`.
  This is a perfect continuation!
  Let's do this. Let's write a python script to generate these climbing vines for a house.
  Let's write down the vertices of the vine stem as a cylinder chain (or simple skin/extrude, or custom meshes), and the leaves as intersecting angled planes.
  Actually, a very light and extremely effective way to represent the vines is to make the stem a series of small, thin wood/vine 3D boxes or cylinders, and the leaves as small double-sided green planes. When textured with our `Foliage` material, they will look 100% organic and realistic!

Let's write Python code to generate this climbing ivy! Let's make sure it's robust and fast. Let's compose the code and execute it to see it in action.

import bpy
import math
import random
from mathutils import Vector, Euler, Matrix

# Let's write the Ivy Generator in Python.
# It climbs up a wall (of size L x W x H) and then climbs the roof slope!
def generate_house_ivy_mesh(name, length=6.0, width=4.0, height=2.5, peak_h=3.6, side='front', x_start=1.0, num_branches=3):
    all_verts = []
    all_faces = []

    # Stem thick
    stem_r = 0.015
    # Leaf size
    leaf_sz = 0.16

    # Helper to add a stem segment (cylinder-like box)
    def add_stem_segment(p1, p2):
        v_offset = len(all_verts)
        # Direction
        dir_v = p2 - p1
        dist = dir_v.length
        if dist < 0.001:
            return

        # Center
        center = p1 + dir_v * 0.5
        # Rotation to align Z-axis with dir_v
        rotation = dir_v.to_track_quat('Z', 'Y').to_euler()

        # Add a small box aligned with the direction
        sx, sy, sz = stem_r, stem_r, dist/2.0
        local_verts = [
            Vector((-sx, -sy, -sz)), Vector((sx, -sy, -sz)),
            Vector((sx, sy, -sz)), Vector((-sx, sy, -sz)),
            Vector((-sx, -sy, sz)), Vector((sx, -sy, sz)),
            Vector((sx, sy, sz)), Vector((-sx, sy, sz))
        ]
        mat = Matrix.Translation(center) @ rotation.to_matrix().to_4x4()
        for lv in local_verts:
            all_verts.append(mat @ lv)

        local_faces = [
            (0, 1, 2, 3), (4, 7, 6, 5), (0, 4, 5, 1),
            (1, 5, 6, 2), (2, 6, 7, 3), (3, 7, 4, 0)
        ]
        for lf in local_faces:
            all_faces.append((lf[0]+v_offset, lf[1]+v_offset, lf[2]+v_offset, lf[3]+v_offset))

    # Helper to add a leaf at a position, with a normal direction
    def add_leaf(pos, normal):
        v_offset = len(all_verts)
        sz = leaf_sz / 2.0
        # Create a diamond-shaped or square angled leaf plane
        # Rotate randomly around normal
        rot_angle = random.uniform(0.0, 2.0 * math.pi)
        # Create frame of reference for the leaf plane
        up = Vector((0, 0, 1))
        tangent = normal.cross(up)
        if tangent.length < 0.01:
            tangent = normal.cross(Vector((0, 1, 0)))
        tangent.normalize()
        bitangent = normal.cross(tangent).normalized()

        # 4 vertices of leaf
        # We tilt the leaf slightly away from the wall (upwards)
        l_verts = [
            pos - tangent * sz - bitangent * sz + normal * 0.02,
            pos + tangent * sz - bitangent * sz + normal * 0.02,
            pos + tangent * sz + bitangent * sz + normal * 0.08,
            pos - tangent * sz + bitangent * sz + normal * 0.08
        ]

        for lv in l_verts:
            all_verts.append(lv)

        all_faces.append((v_offset, v_offset+1, v_offset+2, v_offset+3))

    # Generate each branch
    for b in range(num_branches):
        # Starting point
        curr_x = x_start + random.uniform(-0.15, 0.15)
        curr_z = 0.0

        if side == 'front':
            curr_y = -width/2.0 - 0.02
            wall_normal = Vector((0, -1, 0))
        elif side == 'back':
            curr_y = width/2.0 + 0.02
            wall_normal = Vector((0, 1, 0))
        elif side == 'left':
            curr_x = -length/2.0 - 0.02
            curr_y = x_start
            wall_normal = Vector((-1, 0, 0))
        else: # right
            curr_x = length/2.0 + 0.02
            curr_y = x_start
            wall_normal = Vector((1, 0, 0))

        curr_p = Vector((curr_x, curr_y, curr_z))

        # Grow up the wall
        step_len = 0.18
        steps = int(height / step_len) + 5

        for s in range(steps):
            # Calculate next point
            next_z = curr_p.z + step_len * random.uniform(0.7, 1.2)
            if next_z > height:
                # Reached roof!
                break

            # Random walk along the wall
            if side in ['front', 'back']:
                next_x = curr_p.x + random.uniform(-0.12, 0.12)
                next_y = curr_p.y + random.uniform(-0.01, 0.01) # keep close to wall
            else:
                next_x = curr_p.x + random.uniform(-0.01, 0.01)
                next_y = curr_p.y + random.uniform(-0.12, 0.12)

            next_p = Vector((next_x, next_y, next_z))

            # Add stem
            add_stem_segment(curr_p, next_p)

            # Add leaves
            if random.random() < 0.85:
                # Add 1 or 2 leaves
                add_leaf(next_p, wall_normal)
                if random.random() < 0.5:
                    add_leaf(next_p + Vector((0,0,0.05)), wall_normal)

            curr_p = next_p

        # Reached the roof top. Let's continue climbing along the roof slope!
        if side in ['front', 'back']:
            slope_side = -1 if side == 'front' else 1
            slope_angle = math.atan((peak_h - height) / (width / 2.0))

            # Normal to the roof slope
            roof_normal = Vector((0, -slope_side * math.sin(slope_angle), math.cos(slope_angle)))

            # Grow up towards the peak (Y moves towards 0, Z goes from height to peak_h)
            roof_steps = 15
            for rs in range(roof_steps):
                # How far we are from the peak along Y
                dist_to_peak = abs(curr_p.y)
                if dist_to_peak < 0.1:
                    break

                # Take a step towards Y=0
                step_y = 0.18 * math.cos(slope_angle)
                next_y = curr_p.y + slope_side * step_y
                # If we cross 0, clamp it
                if (slope_side == -1 and next_y > 0.0) or (slope_side == 1 and next_y < 0.0):
                    next_y = 0.0

                # Calculate corresponding Z on the sloped roof
                t_slope = 1.0 - (abs(next_y) / (width/2.0))
                next_z = height + t_slope * (peak_h - height) + random.uniform(0.01, 0.04) # sit slightly above roof
                next_x = curr_p.x + random.uniform(-0.12, 0.12)

                next_p = Vector((next_x, next_y, next_z))

                # Add stem
                add_stem_segment(curr_p, next_p)

                # Add leaves
                add_leaf(next_p, roof_normal)
                if random.random() < 0.7:
                    add_leaf(next_p, roof_normal)

                curr_p = next_p

    # Compile the Ivy mesh
    mesh = bpy.data.meshes.new(name)
    obj = bpy.data.objects.new(name, mesh)
    bpy.context.collection.objects.link(obj)

    vert_tuples = [v.to_tuple() for v in all_verts]
    mesh.from_pydata(vert_tuples, [], all_faces)
    mesh.update()

    # Assign Foliage material
    mat_foliage = bpy.data.materials.get("Foliage")
    if mat_foliage:
        obj.data.materials.append(mat_foliage)

    return obj

# Test ivy on walls
test_ivy = generate_house_ivy_mesh("Ivy_H1", side='front', x_start=0.5, num_branches=4)
# Rotate and place same as House 1 (H1)
test_ivy.rotation_euler = (0, 0, 0.5)
test_ivy.location = Vector((0, 0, 0))

print("Ivy generated successfully!")

Build local-coordinate Ivy generator for climbing walls and slanted roofs


Code executed successfully: Ivy generated successfully!