import bpy, math, random
from mathutils import Vector
from math import radians, sin, cos, pi

# ------------------------------------------------------------
# Reset and scene setup
# ------------------------------------------------------------
random.seed(11)
for obj in list(bpy.data.objects):
    bpy.data.objects.remove(obj, do_unlink=True)
for datablocks in (bpy.data.meshes, bpy.data.curves, bpy.data.materials, bpy.data.cameras, bpy.data.lights):
    # Materials are cleared below only if unused; keep loop conservative for linked safety.
    pass

scene = bpy.context.scene
scene.render.engine = 'BLENDER_EEVEE_NEXT'
scene.render.resolution_x = 700
scene.render.resolution_y = 700
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = 'PNG'
scene.render.film_transparent = False
scene.render.image_settings.color_mode = 'RGBA'
scene.render.filepath = '/tmp/cabin_preview.png'
scene.render.use_file_extension = True
scene.render.image_settings.color_depth = '8'

# Color management
try:
    scene.view_settings.look = 'AgX - Medium High Contrast'
except:
    try: scene.view_settings.look = 'Medium High Contrast'
    except: pass

# World: blue hour, still warm at the cabin
world = bpy.data.worlds.get('World') or bpy.data.worlds.new('World')
scene.world = world
world.use_nodes = True
bg = world.node_tree.nodes.get('Background')
bg.inputs['Color'].default_value = (0.008, 0.018, 0.045, 1)
bg.inputs['Strength'].default_value = 0.28

# Collections
root = scene.collection
collections = {}
for name in ['ENVIRONMENT', 'CABIN', 'ROOF_AND_CHIMNEY', 'DOCK', 'TREES', 'DETAILS', 'LIGHTS']:
    col = bpy.data.collections.new(name)
    root.children.link(col)
    collections[name] = col

def move_to_collection(obj, colname):
    for c in list(obj.users_collection):
        c.objects.unlink(obj)
    collections[colname].objects.link(obj)
    return obj

# ------------------------------------------------------------
# Materials
# ------------------------------------------------------------
def make_mat(name, color, rough=0.5, metallic=0.0, emission=None, emission_strength=0.0):
    m = bpy.data.materials.new(name)
    m.use_nodes = True
    m.diffuse_color = (*color, 1.0)
    bs = m.node_tree.nodes.get('Principled BSDF')
    if bs:
        if 'Base Color' in bs.inputs: bs.inputs['Base Color'].default_value = (*color, 1)
        if 'Roughness' in bs.inputs: bs.inputs['Roughness'].default_value = rough
        if 'Metallic' in bs.inputs: bs.inputs['Metallic'].default_value = metallic
        if 'Specular IOR Level' in bs.inputs: bs.inputs['Specular IOR Level'].default_value = 0.45
        if emission is not None:
            if 'Emission Color' in bs.inputs: bs.inputs['Emission Color'].default_value = (*emission, 1)
            elif 'Emission' in bs.inputs: bs.inputs['Emission'].default_value = (*emission, 1)
            if 'Emission Strength' in bs.inputs: bs.inputs['Emission Strength'].default_value = emission_strength
        if 'Alpha' in bs.inputs: bs.inputs['Alpha'].default_value = 1.0
    return m

wood = make_mat('Wood - honey pine', (0.38, 0.13, 0.038), 0.36)
wood2 = make_mat('Wood - amber variation', (0.52, 0.22, 0.055), 0.38)
wood3 = make_mat('Wood - sunlit variation', (0.66, 0.31, 0.085), 0.4)
wood_dark = make_mat('Wood - deep trim', (0.12, 0.035, 0.012), 0.3)
wood_shadow = make_mat('Wood - wall shadow', (0.18, 0.055, 0.018), 0.65)
door_mat = make_mat('Door - dark walnut', (0.19, 0.055, 0.018), 0.32)
roof_mat = make_mat('Roof - charcoal cedar shingles', (0.055, 0.032, 0.025), 0.58)
roof_alt = make_mat('Roof - shingle variation', (0.085, 0.045, 0.028), 0.6)
stone = make_mat('Stone - cool gray', (0.27, 0.29, 0.28), 0.84)
stone2 = make_mat('Stone - warm gray', (0.38, 0.34, 0.28), 0.88)
stone3 = make_mat('Stone - pale face', (0.49, 0.47, 0.40), 0.9)
mortar = make_mat('Chimney mortar shadow', (0.10, 0.095, 0.085), 0.95)
metal = make_mat('Iron hardware', (0.045, 0.052, 0.05), 0.27, 0.72)
window_glow = make_mat('Windows - warm interior glow', (0.76, 0.22, 0.025), 0.25, 0.0, (1.0, 0.18, 0.015), 4.0)
window_blue = make_mat('Windows - cool reflected glass', (0.055, 0.19, 0.30), 0.16, 0.12)
water_mat = make_mat('Pond - deep blue water', (0.015, 0.105, 0.18), 0.12, 0.18)
water_highlight = make_mat('Pond - moonlit ripple', (0.10, 0.30, 0.36), 0.24, 0.05)
ground_mat = make_mat('Ground - mossy earth', (0.085, 0.18, 0.045), 0.92)
ground_edge = make_mat('Ground - dark soil edge', (0.10, 0.065, 0.025), 1.0)
grass_light = make_mat('Grass - tuft green', (0.16, 0.34, 0.065), 0.82)
pine1 = make_mat('Pine needles - deep', (0.025, 0.12, 0.045), 0.88)
pine2 = make_mat('Pine needles - middle', (0.045, 0.22, 0.075), 0.84)
pine3 = make_mat('Pine needles - lit', (0.10, 0.32, 0.10), 0.8)
bark = make_mat('Tree bark', (0.12, 0.045, 0.015), 0.92)
flower_red = make_mat('Wildflower red', (0.55, 0.025, 0.015), 0.55)
flower_yellow = make_mat('Wildflower gold', (0.85, 0.28, 0.015), 0.5)
smoke_mat = make_mat('Chimney smoke', (0.22, 0.25, 0.27), 1.0)

# ------------------------------------------------------------
# Geometry helpers
# ------------------------------------------------------------
def assign_mat(obj, mat):
    if mat:
        obj.data.materials.append(mat)
    return obj

def cube(name, loc, dims, mat, bevel=0.0, rot=None, col='ENVIRONMENT'):
    bpy.ops.mesh.primitive_cube_add(location=loc)
    o = bpy.context.object
    o.name = name
    o.dimensions = dims
    if rot is not None:
        o.rotation_euler = rot
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    assign_mat(o, mat)
    move_to_collection(o, col)
    if bevel > 0:
        mod = o.modifiers.new('Hand-softened edges', 'BEVEL')
        mod.width = bevel
        mod.segments = 2
        mod.limit_method = 'ANGLE'
    return o

def cyl(name, loc, radius, depth, mat, rot=None, verts=12, bevel=0.0, col='DETAILS'):
    bpy.ops.mesh.primitive_cylinder_add(vertices=verts, radius=radius, depth=depth, location=loc)
    o = bpy.context.object
    o.name = name
    if rot is not None:
        o.rotation_euler = rot
    assign_mat(o, mat)
    move_to_collection(o, col)
    if bevel > 0:
        mod = o.modifiers.new('Soft edges', 'BEVEL')
        mod.width = bevel
        mod.segments = 2
    return o

def cone(name, loc, r1, r2, depth, mat, verts=12, col='TREES'):
    bpy.ops.mesh.primitive_cone_add(vertices=verts, radius1=r1, radius2=r2, depth=depth, location=loc)
    o = bpy.context.object
    o.name = name
    assign_mat(o, mat)
    move_to_collection(o, col)
    mod = o.modifiers.new('Needle edge softness', 'BEVEL')
    mod.width = min(0.04, depth*0.02)
    mod.segments = 1
    return o

def uv_sphere(name, loc, scale, mat, segments=16, rings=8, col='DETAILS'):
    bpy.ops.mesh.primitive_uv_sphere_add(segments=segments, ring_count=rings, location=loc)
    o = bpy.context.object
    o.name = name
    o.scale = scale
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    assign_mat(o, mat)
    move_to_collection(o, col)
    return o

def ico(name, loc, scale, mat, subdivisions=1, col='DETAILS'):
    bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=subdivisions, radius=1, location=loc)
    o = bpy.context.object
    o.name = name
    o.scale = scale
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    assign_mat(o, mat)
    move_to_collection(o, col)
    return o

def beam_between(name, p1, p2, width, mat, col='DETAILS', bevel=0.02):
    p1, p2 = Vector(p1), Vector(p2)
    mid = (p1+p2)*0.5
    vec = p2-p1
    o = cube(name, mid, (width, width, vec.length), mat, bevel=bevel, col=col)
    o.rotation_mode = 'QUATERNION'
    o.rotation_quaternion = Vector((0,0,1)).rotation_difference(vec.normalized())
    return o

def create_prism(name, front_y, back_y, pts, mat, col='CABIN'):
    # pts are (x,z) in winding order
    verts=[]
    for y in (front_y, back_y):
        verts += [(x,y,z) for x,z in pts]
    n=len(pts)
    faces=[tuple(range(n)), tuple(range(n,2*n))[::-1]]
    for i in range(n):
        j=(i+1)%n
        faces.append((i,j,n+j,n+i))
    mesh=bpy.data.meshes.new(name+' Mesh')
    mesh.from_pydata(verts, [], faces)
    mesh.update()
    o=bpy.data.objects.new(name, mesh)
    coldata=collections[col]
    coldata.objects.link(o)
    assign_mat(o, mat)
    return o

# ------------------------------------------------------------
# Environment / bank / pond
# ------------------------------------------------------------
# Large dark floor keeps the orbit grounded
cube('World ground shadow plane', (0, 0, -0.48), (42, 40, 0.35), ground_edge, bevel=0.12, col='ENVIRONMENT')
# Low, softly beveled grassy island
bpy.ops.mesh.primitive_cylinder_add(vertices=64, radius=1, depth=0.42, location=(0,0,-0.16))
island=bpy.context.object
island.name='Mossy irregular cabin clearing'
island.scale=(11.2,9.2,1)
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
assign_mat(island, ground_mat); move_to_collection(island,'ENVIRONMENT')
bev=island.modifiers.new('Rounded earth bank','BEVEL'); bev.width=.28; bev.segments=3
# subtle raised bank along cabin back
uv_sphere('Raised grassy bank behind cabin',(0,3.7,0.10),(5.5,2.1,.35),ground_mat,segments=24,rings=12,col='ENVIRONMENT')

# Pond with concentric slightly irregular rings
cx, cy, rx, ry = 1.2, -6.1, 7.5, 4.25
segments=56
verts=[(cx,cy,0.045)]
ring_radii=[0.36,0.68,1.0]
for rr in ring_radii:
    for i in range(segments):
        a=2*pi*i/segments
        jitter=1.0 + 0.018*sin(i*3.7) + 0.012*cos(i*1.9)
        verts.append((cx+rx*rr*cos(a)*jitter, cy+ry*rr*sin(a)*jitter, 0.055+0.006*sin(i*2.3+rr)))
faces=[]
# center to first ring
for i in range(segments):
    faces.append((0,1+i,1+(i+1)%segments))
for r in range(len(ring_radii)-1):
    start1=1+r*segments; start2=1+(r+1)*segments
    for i in range(segments):
        j=(i+1)%segments
        faces.append((start1+i,start2+i,start2+j,start1+j))
mesh=bpy.data.meshes.new('Pond water mesh')
mesh.from_pydata(verts,[],faces); mesh.update()
pond=bpy.data.objects.new('Quiet blue pond',mesh); collections['ENVIRONMENT'].objects.link(pond); assign_mat(pond,water_mat)
# shoreline stones, larger on the cabin side
rock_mats=[stone,stone2,stone3]
for i in range(18):
    a=radians(200 + i*7.0)
    rr=1.01 + random.uniform(-.015,.015)
    x=cx+rx*rr*cos(a); y=cy+ry*rr*sin(a)
    # keep some rocks especially readable along near shore
    scl=random.uniform(.22,.48)
    rock=ico('Shore stone %02d'%i,(x,y,0.16),(scl*1.25,scl*.85,scl*.60),random.choice(rock_mats),1,'DETAILS')
    rock.rotation_euler[2]=random.random()*pi
# a few shore stones by cabin
for i,(x,y,s) in enumerate([(-3.8,-1.25,.34),(-3.1,-1.55,.24),(4.0,-1.45,.32),(4.7,-1.8,.25)]):
    ico('Cabin bank stone %02d'%i,(x,y,.18),(s*1.2,s,s*.7),random.choice(rock_mats),1,'DETAILS')

# water ripples, stylized geometry that remains readable in GLB
for j,(x,y,sx,sy) in enumerate([(2.9,-6.0,.9,.42),(0.5,-7.5,.55,.24),(-2.2,-5.0,.7,.3)]):
    bpy.ops.mesh.primitive_torus_add(major_radius=1.0, minor_radius=.018, major_segments=40, minor_segments=6, location=(x,y,.085))
    rip=bpy.context.object; rip.name='Moonlit water ripple %02d'%j; rip.scale=(sx,sy,1); bpy.ops.object.transform_apply(location=False,rotation=False,scale=True); assign_mat(rip,water_highlight); move_to_collection(rip,'DETAILS')

# ------------------------------------------------------------
# Cabin log walls
# ------------------------------------------------------------
# dark wall core behind the logs, door and windows
cube('Cabin shadowed wall core',(0,1.0,2.35),(5.82,4.82,3.05),wood_shadow,bevel=.06,col='CABIN')
# stone foundation piers
for i,(x,y) in enumerate([(-2.25,-1.2),(2.25,-1.2),(-2.25,3.15),(2.25,3.15)]):
    cube('Foundation stone pier %02d'%i,(x,y,.38),(1.15,1.05,.65),stone2,bevel=.12,col='CABIN')
# long horizontal logs, alternating tones and slight irregularity
log_rows=10
for row in range(log_rows):
    z=.92+row*.34
    mat=[wood,wood2,wood,wood3][row%4]
    # front and rear walls
    for side,y in [('front',-1.50),('rear',3.50)]:
        length=6.45 + (0.10 if row%2==0 else 0)
        o=cyl('Cabin %s log row %02d'%(side,row),(0,y,z),.235,length,mat,rot=(0,pi/2,0),verts=14,bevel=.018,col='CABIN')
        o.rotation_euler[2] = (0.004 if side=='front' else -0.003) * (row%3)
    # side walls; leave the logs visible behind side window
    for side,x in [('left',-3.0),('right',3.0)]:
        length=5.35 + (0.10 if row%2 else 0)
        cyl('Cabin %s log row %02d'%(side,row),(x,1.0,z),.235,length,mat,rot=(pi/2,0,0),verts=14,bevel=.018,col='CABIN')
# corner upright trim/posts
for x in (-3.06,3.06):
    for y in (-1.54,3.54):
        cube('Square corner timber',(x,y,2.38),(.34,.34,3.25),wood_dark,bevel=.045,col='CABIN')

# gable faces under roof
create_prism('Front triangular gable',-1.61, -1.48,[(-3.0,3.72),(3.0,3.72),(0,5.62)],wood2,'CABIN')
create_prism('Rear triangular gable',3.48,3.61,[(-3.0,3.72),(3.0,3.72),(0,5.62)],wood,'CABIN')
# dark timber line at gable base and decorative braces on front
cube('Front gable sill',(0,-1.68,3.78),(6.15,.18,.22),wood_dark,bevel=.03,col='CABIN')
beam_between('Front left roof brace',(-2.95,-1.72,3.82),(0,-1.72,5.53),.16,wood_dark,'CABIN',.025)
beam_between('Front right roof brace',(0,-1.72,5.53),(2.95,-1.72,3.82),.16,wood_dark,'CABIN',.025)

# ------------------------------------------------------------
# Doors and windows
# ------------------------------------------------------------
# front door, overlaid cleanly on the logs
cube('Front walnut door',(-1.05,-1.765,1.92),(1.45,.13,2.25),door_mat,bevel=.06,col='CABIN')
for i in range(5):
    cube('Door vertical plank %02d'%i,(-1.57+i*.25,-1.848,1.94),(.035,.025,2.10),wood2,bevel=.008,col='CABIN')
# door frame
cube('Door frame left',(-1.84,-1.80,2.02),(.20,.25,2.55),wood_dark,bevel=.035,col='CABIN')
cube('Door frame right',(-.26,-1.80,2.02),(.20,.25,2.55),wood_dark,bevel=.035,col='CABIN')
cube('Door frame header',(-1.05,-1.80,3.24),(1.78,.25,.22),wood_dark,bevel=.035,col='CABIN')
# knob and little metal plate
uv_sphere('Door iron knob',(-.55,-1.95,1.92),(.085,.06,.085),metal,segments=12,rings=6,col='CABIN')
cube('Door latch plate',(-.55,-1.94,1.92),(.10,.025,.18),metal,bevel=.015,col='CABIN')

# front glowing window assembly
cube('Front window dark recess',(1.35,-1.78,2.62),(1.65,.12,1.45),wood_dark,bevel=.035,col='CABIN')
cube('Front window amber glass',(1.35,-1.855,2.62),(1.24,.045,1.04),window_glow,bevel=.015,col='CABIN')
cube('Front window vertical mullion',(1.35,-1.90,2.62),(.10,.045,1.13),wood_dark,bevel=.012,col='CABIN')
cube('Front window horizontal mullion',(1.35,-1.90,2.62),(1.34,.045,.10),wood_dark,bevel=.012,col='CABIN')
# window cross highlights
cube('Front window sill',(1.35,-1.91,1.88),(1.85,.22,.16),wood2,bevel=.025,col='CABIN')
# right side window (faces camera's right-side orbit)
cube('Right window dark recess',(3.16,1.42,2.62),(.13,1.62,1.44),wood_dark,bevel=.035,col='CABIN')
cube('Right window amber glass',(3.235,1.42,2.62),(.045,1.22,1.04),window_glow,bevel=.015,col='CABIN')
cube('Right window vertical mullion',(3.28,1.42,2.62),(.045,.10,1.12),wood_dark,bevel=.012,col='CABIN')
cube('Right window horizontal mullion',(3.28,1.42,2.62),(.045,1.32,.10),wood_dark,bevel=.012,col='CABIN')
cube('Right window sill',(3.30,1.42,1.88),(.23,1.82,.16),wood2,bevel=.025,col='CABIN')

# ------------------------------------------------------------
# Roof with shingle courses
# ------------------------------------------------------------
roof_angle=math.atan2(1.78,3.72)
roof_len=math.sqrt(3.72**2+1.78**2)
# under-panels
cube('Roof left plane',(-1.86,1.0,4.73),(roof_len,6.35,.18),roof_mat,bevel=.035,rot=(0,-roof_angle,0),col='ROOF_AND_CHIMNEY')
cube('Roof right plane',(1.86,1.0,4.73),(roof_len,6.35,.18),roof_mat,bevel=.035,rot=(0,roof_angle,0),col='ROOF_AND_CHIMNEY')
# shingle courses as layered strips following the slopes
for side,sgn in [('left',-1),('right',1)]:
    theta=sgn*roof_angle
    center=Vector((sgn*1.86,1.0,4.73))
    for r in range(7):
        t=-roof_len/2 + .34 + r*.55
        # local x displacement along slope
        pos=center + Vector((math.cos(theta)*t,0,-math.sin(theta)*t))
        strip=cube('%s roof shingle course %02d'%(side,r),pos,(.58,6.42,.075),roof_alt if r%2 else roof_mat,bevel=.018,rot=(0,theta,0),col='ROOF_AND_CHIMNEY')
# ridge and eave caps
cyl('Roof ridge cap',(0,1.0,5.64),.13,6.55,wood_dark,rot=(pi/2,0,0),verts=12,bevel=.02,col='ROOF_AND_CHIMNEY')
for x in (-3.70,3.70):
    z=4.0
    cyl('Roof eave fascia',(x,1.0,z),.115,6.55,wood_dark,rot=(pi/2,0,0),verts=12,bevel=.018,col='ROOF_AND_CHIMNEY')

# porch roof / porch floor
cube('Front porch floor',(-1.05,-2.35,.78),(3.35,1.65,.18),wood2,bevel=.045,col='CABIN')
for i in range(8):
    cube('Porch floor plank %02d'%i,(-2.55+i*.43,-2.37,.89),(.37,1.48,.055),wood if i%2 else wood3,bevel=.012,col='CABIN')
# porch posts and top beam
for x in (-2.55,.45):
    cube('Porch square post',(x,-2.88,2.35),(.22,.22,3.0),wood_dark,bevel=.035,col='CABIN')
cube('Porch front beam',(-1.05,-2.88,3.78),(3.25,.24,.24),wood_dark,bevel=.035,col='CABIN')
# sloping lean-to porch canopy: high at cabin, low at front
porch_angle=math.atan2(.70,1.55)
porch_len=math.sqrt(1.55**2+.70**2)
cube('Small porch canopy',(-1.05,-2.32,3.72),(3.65,porch_len,.14),roof_mat,bevel=.025,rot=(porch_angle,0,0),col='ROOF_AND_CHIMNEY')
cube('Porch canopy front trim',(-1.05,-3.12,3.38),(3.80,.16,.16),wood_dark,bevel=.025,col='ROOF_AND_CHIMNEY')
# steps
cube('Porch upper step',(-1.05,-3.10,.53),(2.10,.55,.25),wood_dark,bevel=.05,col='CABIN')
cube('Porch lower step',(-1.05,-3.48,.36),(2.45,.45,.20),wood2,bevel=.04,col='CABIN')

# ------------------------------------------------------------
# Stone chimney, block-by-block
# ------------------------------------------------------------
chim_x, chim_y = 1.65, 2.10
# shadow interior gives dark grout seen between blocks
cube('Chimney dark core',(chim_x,chim_y,5.42),(1.05,1.05,2.35),mortar,bevel=.03,col='ROOF_AND_CHIMNEY')
chim_mats=[stone,stone2,stone3]
for row in range(6):
    z=4.50+row*.34
    # 2x2 masonry course, slight alternating offsets
    off=0.04 if row%2 else -0.03
    for ix in range(2):
        for iy in range(2):
            x=chim_x-0.26+ix*.52 + off
            y=chim_y-0.26+iy*.52 - off*.4
            block=cube('Chimney stone course %02d block %02d'%(row,ix*2+iy),(x,y,z),(.49,.49,.29),random.choice(chim_mats),bevel=.035,col='ROOF_AND_CHIMNEY')
# upper cap and opening
cube('Chimney cap stone',(chim_x,chim_y,6.47),(1.25,1.25,.22),stone2,bevel=.06,col='ROOF_AND_CHIMNEY')
cube('Chimney black opening',(chim_x,chim_y,6.60),(.72,.72,.035),mortar,bevel=.01,col='ROOF_AND_CHIMNEY')
# smoke puffs, deliberately restrained
for i,(x,y,z,s) in enumerate([(1.62,2.10,6.95,.34),(1.78,2.16,7.45,.46),(1.55,2.02,7.98,.58)]):
    puff=uv_sphere('Chimney smoke puff %02d'%i,(x,y,z),(s*.8,s*.75,s),smoke_mat,segments=16,rings=8,col='DETAILS')
    puff.rotation_euler[2]=i*.7

# ------------------------------------------------------------
# Dock into the pond
# ------------------------------------------------------------
dock_x=3.35
# structural rails below boards
cube('Dock left stringer',(dock_x-.82,-4.65,.34),(.18,5.55,.28),wood_dark,bevel=.025,col='DOCK')
cube('Dock right stringer',(dock_x+.82,-4.65,.34),(.18,5.55,.28),wood_dark,bevel=.025,col='DOCK')
# planks with gaps and alternating warm colors
for i in range(10):
    y=-2.20-i*.52
    cube('Dock plank %02d'%i,(dock_x,y,.52),(1.72,.45,.17),wood3 if i%3==0 else (wood2 if i%3==1 else wood),bevel=.025,col='DOCK')
# end cap and small landing against bank
cube('Dock bank landing',(dock_x,-1.87,.54),(2.05,.42,.19),wood_dark,bevel=.025,col='DOCK')
# pilings
for i,(x,y) in enumerate([(dock_x-.82,-2.25),(dock_x+.82,-2.25),(dock_x-.82,-7.05),(dock_x+.82,-7.05)]):
    cyl('Dock piling %02d'%i,(x,y,-.12),.14,1.65,bark,verts=12,bevel=.018,col='DOCK')
# metal cleats at dock head
for x in (dock_x-.55,dock_x+.55):
    cyl('Dock iron cleat',(x,-2.02,.70),.055,.16,metal,verts=10,bevel=.01,col='DOCK')
    cube('Dock cleat foot',(x,-2.02,.63),(.18,.12,.04),metal,bevel=.01,col='DOCK')

# ------------------------------------------------------------
# Trees
# ------------------------------------------------------------
def pine_tree(name,x,y,s,rot=0):
    # ground marker / roots
    cyl(name+' trunk',(x,y,1.25*s),.22*s,2.5*s,bark,verts=10,bevel=.025,col='TREES')
    # three broad tiers with clear silhouette
    tiers=[(2.35,1.65,0.0,2.7),(3.65,1.38,.0,2.55),(4.85,1.05,.0,2.35),(5.85,.64,.0,1.75)]
    mats=[pine1,pine2,pine1,pine3]
    for j,(zbase,rad,r2,dep) in enumerate(tiers):
        o=cone(name+' needle tier %02d'%j,(x,y,zbase*s),rad*s,r2*s,dep*s,mats[j],verts=14,col='TREES')
        o.rotation_euler[2]=rot + (j%2)*.15
    # small root flare stones
    for j in range(3):
        a=rot+j*2.1
        ico(name+' root rock %02d'%j,(x+cos(a)*.32*s,y+sin(a)*.32*s,.13),(0.25*s,.18*s,.12*s),bark,1,'TREES')

pine_tree('Tall pine left',-6.2,2.9,1.18,.3)
pine_tree('Tall pine right',6.6,4.0,1.35,1.1)
pine_tree('Pondside pine',-6.0,-3.7,.86,2.0)
# a smaller tree peeking behind the dock for depth
pine_tree('Small pine far shore',7.5,-2.6,.72,.8)

# reeds and wildflowers near the pond edge
for i in range(18):
    x=-5.8 + (i%9)*.65 + random.uniform(-.12,.12)
    y=-2.45 + random.uniform(-.12,.12)
    h=random.uniform(.38,.85)
    beam_between('Shore grass blade %02d'%i,(x,y,.12),(x+random.uniform(-.10,.10),y+random.uniform(-.05,.05),.12+h),.035,grass_light,'DETAILS',.008)
for i,(x,y,colmat) in enumerate([(-4.9,-2.45,flower_red),(-4.3,-2.35,flower_yellow),(-3.7,-2.50,flower_red),(5.0,-2.2,flower_yellow),(5.5,-2.0,flower_red)]):
    beam_between('Wildflower stem %02d'%i,(x,y,.1),(x,y,.55),.018,grass_light,'DETAILS',.004)
    ico('Wildflower bloom %02d'%i,(x,y,.62),(.12,.12,.10),colmat,1,'DETAILS')

# ------------------------------------------------------------
# Warm porch lantern and scene lights
# ------------------------------------------------------------
# lantern hanging under porch beam
cube('Lantern glowing glass',(.20,-2.90,2.72),(.26,.22,.42),window_glow,bevel=.045,col='DETAILS')
cube('Lantern top',(.20,-2.90,2.96),(.34,.28,.07),metal,bevel=.02,col='DETAILS')
cube('Lantern bottom',(.20,-2.90,2.48),(.34,.28,.07),metal,bevel=.02,col='DETAILS')
for x in (.20,):
    cube('Lantern side bar',(x-.14,-2.90,2.72),(.035,.035,.40),metal,bevel=.008,col='DETAILS')
    cube('Lantern side bar',(x+.14,-2.90,2.72),(.035,.035,.40),metal,bevel=.008,col='DETAILS')
# little hanging chain
cyl('Lantern hanging chain',(.20,-2.90,3.20),.025,.42,metal,verts=8,col='DETAILS')

def point_light(name, loc, energy, color, radius):
    data=bpy.data.lights.new(name,'POINT'); data.energy=energy; data.color=color; data.shadow_soft_size=radius
    o=bpy.data.objects.new(name,data); collections['LIGHTS'].objects.link(o); o.location=loc; return o

def area_light(name, loc, energy, color, size, target):
    data=bpy.data.lights.new(name,'AREA'); data.energy=energy; data.color=color; data.shape='DISK'; data.size=size
    o=bpy.data.objects.new(name,data); collections['LIGHTS'].objects.link(o); o.location=loc
    o.rotation_euler=(Vector(target)-o.location).to_track_quat('-Z','Y').to_euler(); return o

point_light('Warm porch lantern light',(.20,-3.0,2.72),170,(1.0,.22,.035),.55)
point_light('Front window interior glow',(1.35,-1.50,2.65),100,(1.0,.16,.025),.5)
point_light('Side window interior glow',(2.70,1.42,2.6),80,(1.0,.20,.035),.5)
area_light('Large warm sunset key',(4.0,-8.0,12.0),1050,(1.0,.38,.16),7.0,(0,0,1.8))
area_light('Cool blue fill',(-8.0,-2.0,8.0),850,(.16,.30,1.0),8.0,(0,0,2.0))
area_light('Soft rim from pond',(2.0,5.0,9.0),700,(.20,.38,1.0),6.0,(0,0,2.0))
# sun for readable all-around orbit shadows
sun_data=bpy.data.lights.new('Low warm evening sun','SUN'); sun_data.energy=1.6; sun_data.color=(1.0,.35,.12); sun_data.angle=radians(12)
sun=bpy.data.objects.new('Low warm evening sun',sun_data); collections['LIGHTS'].objects.link(sun); sun.rotation_euler=(radians(28),radians(-28),radians(-28))

# ------------------------------------------------------------
# Camera and presentation settings
# ------------------------------------------------------------
def look_at(obj, target):
    direction=Vector(target)-obj.location
    obj.rotation_euler=direction.to_track_quat('-Z','Y').to_euler()

cam_data=bpy.data.cameras.new('Cabin presentation camera')
cam=bpy.data.objects.new('Cabin presentation camera',cam_data)
collections['LIGHTS'].objects.link(cam)
cam.location=(13.0,-18.5,10.0)
cam_data.lens=52
cam_data.sensor_width=36
look_at(cam,(0.15,-1.25,2.65))
scene.camera=cam
# composition slight shift upward
cam_data.dof.use_dof=False

# Metadata and render tuning
scene['scene_title']='Warm wooden cabin beside a pond'
scene['scene_description']='Hand-built log cabin with stone chimney, dock, pond, porch lantern, and four pine trees.'
scene['asset_notes']='Core forms are modeled as mesh geometry for interactive GLB readability.'
scene.render.filepath='/tmp/cabin_preview.png'
# set camera clipping generous for orbit
cam_data.clip_start=.1; cam_data.clip_end=200

# Select cabin for convenient inspection
bpy.ops.object.select_all(action='DESELECT')
for o in bpy.data.objects:
    o.select_set(False)
# save initial scene
bpy.ops.wm.save_as_mainfile(filepath='/tmp/warm_cabin_pond.blend')

result={'objects':len(bpy.data.objects),'meshes':len(bpy.data.meshes),'materials':len(bpy.data.materials),'camera':cam.name,'file':'/tmp/warm_cabin_pond.blend'}


Traceback (most recent call last):
  File "C:\Users\proud\Downloads\Telegram Desktop\blender-bench-runner-windows\.runner\blender-mcp-v1.0.0\addon\mcp_to_blender_server.py", line 230, in _execute_code
    exec(code, namespace)
    ~~~~^^^^^^^^^^^^^^^^^
  File "<string>", line 16, in <module>
TypeError: bpy_struct: item.attr = val: enum "BLENDER_EEVEE_NEXT" not found in ('BLENDER_EEVEE', 'BLENDER_WORKBENCH', 'CYCLES')