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

# -------------------------
# Scene reset and utilities
# -------------------------
random.seed(7)
for obj in list(bpy.data.objects):
    bpy.data.objects.remove(obj, do_unlink=True)
scene = bpy.context.scene
scene.render.engine = 'BLENDER_EEVEE'
scene.render.resolution_x = 1536
scene.render.resolution_y = 1536
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = 'PNG'
scene.render.film_transparent = False
scene.render.use_file_extension = True
scene.render.image_settings.color_mode = 'RGBA'
scene.render.image_settings.color_depth = '8'
scene.world.color = (0.004, 0.002, 0.001)
try:
    scene.view_settings.look = 'AgX - Medium High Contrast'
except Exception:
    pass
scene.view_settings.exposure = 0.35
scene.view_settings.gamma = 1.0

# collections
for c in list(scene.collection.children):
    scene.collection.children.unlink(c)
def new_collection(name):
    c = bpy.data.collections.new(name)
    scene.collection.children.link(c)
    return c
COL_ARCH = new_collection('ARCHITECTURE | dark ground and Cornell fabric')
COL_HERO = new_collection('HERO | Utah Teapot')
COL_CAST = new_collection('CAST | Bunny Suzanne and Raytracing Sphere')
COL_PROP = new_collection('PROPS | book fruit candlestick and plaques')
COL_LIGHT = new_collection('LIGHT | single warm window shaft')

def link_to(obj, coll):
    for c in list(obj.users_collection):
        c.objects.unlink(obj)
    coll.objects.link(obj)
    return obj

def set_smooth(obj):
    if obj.type == 'MESH':
        for p in obj.data.polygons:
            p.use_smooth = True

def safe_set(node, name, value):
    if node and name in node.inputs:
        node.inputs[name].default_value = value

def mat_principled(name, base, rough=0.4, metallic=0.0, coat=0.0, emission=None, emission_strength=0.0):
    m = bpy.data.materials.new(name)
    m.use_nodes = True
    nt = m.node_tree; nt.nodes.clear()
    out = nt.nodes.new('ShaderNodeOutputMaterial')
    bs = nt.nodes.new('ShaderNodeBsdfPrincipled')
    bs.location = (-220, 0); out.location = (40,0)
    safe_set(bs, 'Base Color', (*base,1))
    safe_set(bs, 'Roughness', rough)
    safe_set(bs, 'Metallic', metallic)
    safe_set(bs, 'Coat Weight', coat)
    safe_set(bs, 'Coat Roughness', max(0.04, rough*0.55))
    if emission is not None:
        safe_set(bs, 'Emission Color', (*emission,1))
        safe_set(bs, 'Emission Strength', emission_strength)
    nt.links.new(bs.outputs['BSDF'], out.inputs['Surface'])
    return m

def mat_noise(name, base1, base2, rough=0.5, noise_scale=5.0, bump_strength=0.08, detail=3.0, metallic=0.0, coat=0.0):
    m = bpy.data.materials.new(name); m.use_nodes=True
    nt=m.node_tree; nt.nodes.clear()
    out=nt.nodes.new('ShaderNodeOutputMaterial'); out.location=(420,0)
    bs=nt.nodes.new('ShaderNodeBsdfPrincipled'); bs.location=(160,0)
    safe_set(bs,'Roughness',rough); safe_set(bs,'Metallic',metallic); safe_set(bs,'Coat Weight',coat)
    safe_set(bs,'Coat Roughness',max(.04,rough*.5))
    noise=nt.nodes.new('ShaderNodeTexNoise'); noise.location=(-420,20); safe_set(noise,'Scale',noise_scale); safe_set(noise,'Detail',detail); safe_set(noise,'Roughness',.72)
    ramp=nt.nodes.new('ShaderNodeValToRGB'); ramp.location=(-180,80)
    ramp.color_ramp.elements[0].color=(*base1,1); ramp.color_ramp.elements[0].position=.28
    ramp.color_ramp.elements[1].color=(*base2,1); ramp.color_ramp.elements[1].position=.72
    bump=nt.nodes.new('ShaderNodeBump'); bump.location=(-20,-100); safe_set(bump,'Strength',bump_strength); safe_set(bump,'Distance',.08)
    nt.links.new(noise.outputs['Fac'],ramp.inputs['Fac']); nt.links.new(ramp.outputs['Color'],bs.inputs['Base Color']); nt.links.new(noise.outputs['Fac'],bump.inputs['Height']); nt.links.new(bump.outputs['Normal'],bs.inputs['Normal']); nt.links.new(bs.outputs['BSDF'],out.inputs['Surface'])
    return m

def mat_fabric(name, color):
    m=bpy.data.materials.new(name); m.use_nodes=True
    nt=m.node_tree; nt.nodes.clear()
    out=nt.nodes.new('ShaderNodeOutputMaterial'); out.location=(460,0)
    bs=nt.nodes.new('ShaderNodeBsdfPrincipled'); bs.location=(180,0)
    safe_set(bs,'Base Color',(*color,1)); safe_set(bs,'Roughness',.62); safe_set(bs,'Sheen Weight',.18); safe_set(bs,'Sheen Roughness',.4)
    noise=nt.nodes.new('ShaderNodeTexNoise'); noise.location=(-420,40); safe_set(noise,'Scale',24); safe_set(noise,'Detail',2.5); safe_set(noise,'Roughness',.75)
    ramp=nt.nodes.new('ShaderNodeValToRGB'); ramp.location=(-180,80)
    c1=tuple(min(1,x*.65) for x in color); c2=tuple(min(1,x*1.18+.02) for x in color)
    ramp.color_ramp.elements[0].color=(*c1,1); ramp.color_ramp.elements[0].position=.30
    ramp.color_ramp.elements[1].color=(*c2,1); ramp.color_ramp.elements[1].position=.70
    bump=nt.nodes.new('ShaderNodeBump'); bump.location=(0,-80); safe_set(bump,'Strength',.18); safe_set(bump,'Distance',.035)
    nt.links.new(noise.outputs['Fac'],ramp.inputs['Fac']); nt.links.new(ramp.outputs['Color'],bs.inputs['Base Color']); nt.links.new(noise.outputs['Fac'],bump.inputs['Height']); nt.links.new(bump.outputs['Normal'],bs.inputs['Normal']); nt.links.new(bs.outputs['BSDF'],out.inputs['Surface'])
    return m

def add_bevel(obj, width=0.05, segments=3, apply=False):
    mod=obj.modifiers.new('soft worn edges','BEVEL'); mod.width=width; mod.segments=segments
    if apply:
        bpy.context.view_layer.objects.active=obj; obj.select_set(True)
        try: bpy.ops.object.modifier_apply(modifier=mod.name)
        except: pass
        obj.select_set(False)
    return obj

def mesh_obj(name, verts, faces, mat=None, coll=COL_CAST):
    me=bpy.data.meshes.new(name+' Mesh'); me.from_pydata(verts, [], faces); me.update()
    obj=bpy.data.objects.new(name, me); coll.objects.link(obj)
    if mat: me.materials.append(mat)
    return obj

def add_cube(name, loc, scale, mat, coll=COL_ARCH, bevel=0.0):
    bpy.ops.mesh.primitive_cube_add(location=loc)
    o=bpy.context.object; o.name=name; o.dimensions=scale
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    link_to(o,coll)
    if mat: o.data.materials.append(mat)
    if bevel: add_bevel(o,bevel,3,True)
    return o

def add_uv(name, loc, scale, mat, coll=COL_CAST, segments=48, rings=32, bevel=0.0):
    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)
    link_to(o,coll)
    if mat: o.data.materials.append(mat)
    set_smooth(o)
    if bevel: add_bevel(o,bevel,2,True)
    return o

def add_cyl(name, loc, radius, depth, mat, coll=COL_PROP, vertices=64, bevel=0.0, rotation=None):
    bpy.ops.mesh.primitive_cylinder_add(vertices=vertices, radius=radius, depth=depth, location=loc, rotation=rotation or (0,0,0))
    o=bpy.context.object; o.name=name; link_to(o,coll)
    if mat: o.data.materials.append(mat)
    set_smooth(o)
    if bevel: add_bevel(o,bevel,3,True)
    return o

def add_torus(name, loc, major, minor, mat, coll=COL_PROP, rotation=None):
    bpy.ops.mesh.primitive_torus_add(major_radius=major, minor_radius=minor, major_segments=72, minor_segments=16, location=loc, rotation=rotation or (0,0,0))
    o=bpy.context.object; o.name=name; link_to(o,coll)
    if mat:o.data.materials.append(mat)
    set_smooth(o); return o

def create_lathe(name, profile, mat, coll=COL_HERO, segments=64):
    verts=[]
    for r,z in profile:
        for i in range(segments):
            a=2*pi*i/segments; verts.append((r*cos(a),r*sin(a),z))
    faces=[]; nr=len(profile)
    for j in range(nr-1):
        for i in range(segments):
            ni=(i+1)%segments; faces.append((j*segments+i,j*segments+ni,(j+1)*segments+ni,(j+1)*segments+i))
    faces.append(tuple(reversed([i for i in range(segments)]))); faces.append(tuple([(nr-1)*segments+i for i in range(segments)]))
    o=mesh_obj(name,verts,faces,mat,coll); set_smooth(o); add_bevel(o,.025,2,True); return o

def create_tube(name, points, radii, mat, coll=COL_HERO, segments=32):
    pts=[Vector(p) for p in points]; verts=[]
    for i,p in enumerate(pts):
        tangent=(pts[1]-pts[0]).normalized() if i==0 else ((pts[-1]-pts[-2]).normalized() if i==len(pts)-1 else (pts[i+1]-pts[i-1]).normalized())
        basis1=Vector((0,1,0))
        if abs(tangent.dot(basis1))>.92: basis1=Vector((1,0,0))
        basis1=(basis1-tangent*tangent.dot(basis1)).normalized(); basis2=tangent.cross(basis1).normalized()
        for k in range(segments):
            a=2*pi*k/segments; q=p+radii[i]*(cos(a)*basis1+sin(a)*basis2); verts.append(tuple(q))
    faces=[]
    for i in range(len(pts)-1):
        for k in range(segments):
            nk=(k+1)%segments; a=i*segments+k; b=i*segments+nk; c=(i+1)*segments+nk; d=(i+1)*segments+k; faces.append((a,b,c,d))
    faces.append(tuple(reversed([k for k in range(segments)]))); last=(len(pts)-1)*segments; faces.append(tuple(last+k for k in range(segments)))
    o=mesh_obj(name,verts,faces,mat,coll); set_smooth(o); add_bevel(o,.015,2,True); return o

def create_fabric_panel(name,x0,x1,z0,z1,ybase,mat,folds=6,nx=28,nz=24,coll=COL_ARCH,phase=0):
    verts=[]
    for i in range(nx+1):
        u=i/nx; x=x0+(x1-x0)*u
        for j in range(nz+1):
            v=j/nz; z=z0+(z1-z0)*v; amp=(.03+.16*v)
            y=ybase+amp*sin(2*pi*folds*u+phase)+.035*sin(2*pi*(folds*2.2)*u+1.3*v); z2=z-.035*sin(2*pi*u+phase)*(1-v)*.7; verts.append((x,y,z2))
    faces=[]
    for i in range(nx):
        for j in range(nz):
            a=i*(nz+1)+j; b=(i+1)*(nz+1)+j; c=(i+1)*(nz+1)+j+1; d=i*(nz+1)+j+1; faces.append((a,b,c,d))
    o=mesh_obj(name,verts,faces,mat,coll); sol=o.modifiers.new('cloth thickness','SOLIDIFY'); sol.thickness=.035; bev=o.modifiers.new('soft cloth edge','BEVEL'); bev.width=.012; bev.segments=2; return o

def create_leaf(name, points_xz, yfront, thickness, mat, coll=COL_CAST, bevel=.025):
    verts=[(x,yfront-thickness/2,z) for x,z in points_xz]+[(x,yfront+thickness/2,z) for x,z in points_xz]; n=len(points_xz); faces=[tuple(reversed(range(n))),tuple(n+i for i in range(n))]
    for i in range(n): faces.append((i,(i+1)%n,n+(i+1)%n,n+i))
    o=mesh_obj(name,verts,faces,mat,coll); add_bevel(o,bevel,3,True); set_smooth(o); return o

def add_curve_tube(name, coords, radius, mat, coll=COL_HERO):
    cu=bpy.data.curves.new(name+' Curve','CURVE'); cu.dimensions='3D'; cu.resolution_u=24; cu.bevel_depth=radius; cu.bevel_resolution=6
    sp=cu.splines.new('BEZIER'); sp.bezier_points.add(len(coords)-1)
    for bp,co in zip(sp.bezier_points,coords): bp.co=co; bp.handle_left_type='AUTO'; bp.handle_right_type='AUTO'
    o=bpy.data.objects.new(name,cu); coll.objects.link(o); cu.materials.append(mat); bpy.context.view_layer.objects.active=o; o.select_set(True)
    try: bpy.ops.object.convert(target='MESH')
    except: pass
    o=bpy.context.object; o.name=name; set_smooth(o); return o

def add_text(name, body, loc, size, mat, coll=COL_PROP, extrude=.008, align='CENTER'):
    bpy.ops.object.text_add(location=loc, rotation=(pi/2,0,0))
    o=bpy.context.object; o.name=name; o.data.body=body; o.data.align_x=align; o.data.align_y='CENTER'; o.data.size=size; o.data.extrude=extrude; o.data.bevel_depth=.002; o.data.bevel_resolution=2; o.data.materials.append(mat); link_to(o,coll); bpy.context.view_layer.objects.active=o; o.select_set(True)
    try: bpy.ops.object.convert(target='MESH')
    except: pass
    o=bpy.context.object; o.name=name; return o

# -------------------------
# Materials
# -------------------------
mat_table = mat_noise('Walnut table | worn satin', (0.012,0.004,0.002), (0.065,0.018,0.008), rough=.3, noise_scale=3.2, bump_strength=.16, detail=5)
mat_table_edge = mat_noise('Walnut edge | aged', (0.008,0.002,0.001), (0.035,0.008,0.003), rough=.36, noise_scale=5, bump_strength=.12, detail=4)
mat_back = mat_noise('Deep umber ground', (0.004,0.002,0.001), (0.018,0.009,0.006), rough=.65, noise_scale=2.5, bump_strength=.04, detail=2)
mat_central = mat_fabric('Neutral grey linen | Cornell middle', (0.18,0.15,0.12))
mat_red = mat_fabric('Cornell red velvet | left drape', (0.38,0.012,0.009))
mat_green = mat_fabric('Cornell green velvet | right drape', (0.008,0.25,0.045))
mat_cream = mat_principled('Old ivory ceramic', (0.68,0.56,0.36), rough=.18, metallic=.02, coat=.34)
mat_cream2 = mat_principled('Ceramic shadow blue', (0.06,0.12,0.16), rough=.22, metallic=.06, coat=.22)
mat_gold = mat_principled('Aged brass highlights', (0.34,0.13,0.025), rough=.24, metallic=.78, coat=.22)
mat_bronze = mat_noise('Patinated bronze | Suzanne', (0.035,0.008,0.002), (0.16,0.047,0.012), rough=.27, noise_scale=6, bump_strength=.08, detail=3, metallic=.82, coat=.22)
mat_stone = mat_noise('Warm grey stone | Stanford bunny', (0.18,0.15,0.115), (0.40,0.34,0.25), rough=.43, noise_scale=4, bump_strength=.12, detail=3)
mat_ear = mat_principled('Rabbit inner ear', (0.19,0.045,0.032), rough=.5)
mat_dark = mat_principled('Graphite black', (0.006,0.004,0.003), rough=.3, metallic=.12)
mat_ivory = mat_principled('Aged book pages', (0.62,0.50,0.31), rough=.55)
mat_page_edge = mat_principled('Page shadow lines', (0.16,0.055,0.012), rough=.4)
mat_wax = mat_principled('Old beeswax', (0.67,0.40,0.13), rough=.38)
mat_flame = mat_principled('Candle flame (warm glow)', (1.0,0.18,0.015), rough=.25, emission=(1.0,.07,.005), emission_strength=2.5)
mat_apple = mat_noise('Aged russet fruit', (0.18,0.008,0.003), (0.48,0.045,0.008), rough=.3, noise_scale=5, bump_strength=.09, detail=3)
mat_orange = mat_principled('Muted amber fruit', (0.55,0.13,0.018), rough=.34, coat=.12)
mat_leaf = mat_principled('Fruit leaf green', (0.015,0.12,0.018), rough=.42)
mat_label = mat_principled('Museum label brass', (0.32,0.16,0.035), rough=.3, metallic=.72)
mat_label_dark = mat_principled('Plaque dark backing', (0.025,0.012,0.005), rough=.42, metallic=.2)
mat_check_dark = mat_principled('Raytracing sphere | graphite squares', (0.008,0.012,0.014), rough=.11, metallic=.94, coat=.34)
mat_check_light = mat_principled('Raytracing sphere | ivory squares', (0.46,0.40,0.30), rough=.10, metallic=.92, coat=.36)

# -------------------------
# Architecture / dark ground
# -------------------------
top=add_cube('Dark walnut tabletop', (0,0,0.34), (14.5,8.4,.68), mat_table, COL_ARCH, bevel=.16)
apron=add_cube('Table front edge', (0,-4.02,-.25), (14.2,.34,1.15), mat_table_edge, COL_ARCH, bevel=.08)
edge_inlay=add_cube('Table edge warm highlight', (0,-4.205,.08), (13.3,.018,.055), mat_gold, COL_ARCH, bevel=.015)
back=add_cube('Dark gallery wall', (0,4.55,3.25), (15.2,.22,8.5), mat_back, COL_ARCH, bevel=.04)
floor=add_cube('Dark ground beyond table', (0,1.2,-.18), (18,15,.18), mat_back, COL_ARCH, bevel=.02)
red_panel=create_fabric_panel('RED VELVET DRAPE | Cornell left', -6.9,-2.35,.72,6.45,4.27,mat_red,folds=7,nx=32,nz=28,phase=.4)
green_panel=create_fabric_panel('GREEN VELVET DRAPE | Cornell right', 2.35,6.9,.72,6.45,4.24,mat_green,folds=7,nx=32,nz=28,phase=1.7)
central_panel=create_fabric_panel('NEUTRAL GREY DRAPE | Cornell middle', -2.38,2.38,.72,6.45,4.30,mat_central,folds=4,nx=24,nz=28,phase=.2)
red_pool=create_fabric_panel('Red cloth pool on table',-6.4,-3.25,.69,1.0,1.2,mat_red,folds=5,nx=22,nz=3,coll=COL_ARCH,phase=1.1); red_pool.rotation_euler=(pi/2,0,0)
green_pool=create_fabric_panel('Green cloth pool on table',3.2,6.3,.69,1.0,1.25,mat_green,folds=5,nx=22,nz=3,coll=COL_ARCH,phase=.6); green_pool.rotation_euler=(pi/2,0,0)

# -------------------------
# Utah Teapot hero
# -------------------------
body_profile=[(.70,0.00),(1.03,.10),(1.34,.34),(1.53,.66),(1.60,1.00),(1.57,1.32),(1.44,1.60),(1.22,1.83),(.88,2.00)]
teapot_body=create_lathe('UTAH TEAPOT | squat ceramic body',body_profile,mat_cream,COL_HERO,segments=72); teapot_body.location=(0.0,.25,.72); teapot_body['graphics_history']='Utah Teapot, 1975'
foot=add_torus('Teapot foot ring',(0,.25,.79),.70,.075,mat_cream2,COL_HERO)
lid_profile=[(.66,1.96),(.88,1.99),(.98,2.06),(.96,2.14),(.80,2.24),(.54,2.34),(.22,2.39)]
lid=create_lathe('Teapot domed lid',lid_profile,mat_cream,COL_HERO,segments=72); lid.location=(0,.25,.72)
rim=add_torus('Teapot lid blue rim',(0,.25,2.71),.79,.055,mat_cream2,COL_HERO)
knob_stem=add_cyl('Teapot lid knob stem',(0,.25,3.12),.085,.24,mat_cream2,COL_HERO,vertices=32,bevel=.02)
knob=add_uv('Teapot lid knob',(0,.25,3.28),(.25,.25,.19),mat_gold,COL_HERO,segments=40,rings=24)
belt1=add_torus('Teapot cobalt shoulder band',(0,.25,2.31),.93,.035,mat_cream2,COL_HERO)
belt2=add_torus('Teapot cobalt lower band',(0,.25,1.30),1.55,.028,mat_cream2,COL_HERO)
handle_points=[(1.00,.36,2.48),(1.55,.40,2.72),(2.22,.43,2.59),(2.65,.43,2.18),(2.62,.43,1.68),(2.28,.42,1.27),(1.64,.38,1.13),(1.08,.34,1.24)]
handle=add_curve_tube('UTAH TEAPOT | open handle',handle_points,.17,mat_cream,COL_HERO)
spout_points=[(-1.00,.10,2.02),(-1.45,.03,2.10),(-1.88,-.02,2.28),(-2.24,-.08,2.52),(-2.50,-.10,2.84),(-2.57,-.10,3.03)]
spout_r=[.57,.49,.38,.29,.21,.14]
spout=create_tube('UTAH TEAPOT | rising spout',spout_points,spout_r,mat_cream,COL_HERO,segments=40)
mouth=add_uv('Teapot dark spout opening',(-2.585,-.10,3.045),(.105,.105,.035),mat_dark,COL_HERO,segments=32,rings=16); mouth.rotation_euler[1]=-.12
medal=add_uv('Teapot maker medallion',(-.02,-1.36,1.72),(.22,.035,.22),mat_gold,COL_HERO,segments=32,rings=16)

# -------------------------
# Stanford Bunny approximation
# -------------------------
bunny_body=add_uv('STANFORD BUNNY | rounded crouching body',(-3.65,.42,1.54),(1.36,.82,.88),mat_stone,COL_CAST); bunny_body.rotation_euler[2]=-.10
bunny_haunch=add_uv('Bunny rear haunch',(-2.82,.52,1.36),(.84,.72,.72),mat_stone,COL_CAST)
bunny_head=add_uv('Bunny compact head',(-4.10,.05,2.16),(.73,.64,.70),mat_stone,COL_CAST)
bunny_muzzle=add_uv('Bunny muzzle',(-4.58,-.36,2.07),(.42,.40,.34),mat_stone,COL_CAST,segments=40,rings=24)
left_ear_pts=[(-4.43,2.57),(-4.60,3.08),(-4.52,3.58),(-4.31,3.88),(-4.11,3.48),(-4.10,3.02),(-4.16,2.63)]
right_ear_pts=[(-3.93,2.62),(-3.91,3.10),(-3.66,3.53),(-3.34,3.70),(-3.27,3.34),(-3.43,2.89),(-3.57,2.61)]
ear1=create_leaf('Bunny folded ear | near',left_ear_pts,-.03,.20,mat_stone,COL_CAST,.035); ear2=create_leaf('Bunny folded ear | far',right_ear_pts,.08,.20,mat_stone,COL_CAST,.035)
inner1=[(-4.38,2.72),(-4.45,3.15),(-4.43,3.48),(-4.31,3.66),(-4.24,3.35),(-4.24,2.86)]
inner2=[(-3.79,2.76),(-3.78,3.10),(-3.62,3.38),(-3.43,3.52),(-3.48,3.18),(-3.58,2.80)]
create_leaf('Bunny inner ear near',inner1,-.145,.025,mat_ear,COL_CAST,.008); create_leaf('Bunny inner ear far',inner2,-.075,.025,mat_ear,COL_CAST,.008)
eye=add_uv('Bunny glossy eye',(-4.34,-.54,2.37),(.085,.055,.085),mat_dark,COL_CAST,segments=24,rings=16); nose=add_uv('Bunny nose',(-4.90,-.50,2.08),(.10,.075,.075),mat_dark,COL_CAST,segments=24,rings=16)
add_uv('Bunny tucked paw near',(-4.06,-.48,1.04),(.42,.35,.24),mat_stone,COL_CAST); add_uv('Bunny tucked paw far',(-3.45,-.50,1.00),(.40,.32,.22),mat_stone,COL_CAST)
bunny_plinth=add_cyl('Bunny oval plinth',(-3.55,.36,.79),1.18,.16,mat_dark,COL_CAST,vertices=64,bevel=.045); bunny_plinth.scale.y=.72; bpy.context.view_layer.objects.active=bunny_plinth; bunny_plinth.select_set(True); bpy.ops.object.transform_apply(location=False,rotation=False,scale=True); bunny_plinth.select_set(False)

# -------------------------
# Suzanne bust
# -------------------------
ped_base=add_cyl('Suzanne pedestal base',(3.25,.48,.82),1.10,.24,mat_dark,COL_CAST,vertices=64,bevel=.06); ped_ring=add_torus('Suzanne pedestal brass collar',(3.25,.48,.99),.76,.065,mat_gold,COL_CAST); ped_stem=add_cyl('Suzanne pedestal stem',(3.25,.48,1.25),.60,.48,mat_bronze,COL_CAST,vertices=64,bevel=.045); ped_cap=add_cyl('Suzanne pedestal cap',(3.25,.48,1.53),.86,.12,mat_dark,COL_CAST,vertices=64,bevel=.035)
bpy.ops.mesh.primitive_monkey_add(location=(3.25,.48,2.42))
suz=bpy.context.object; suz.name='SUZANNE | Blender monkey bust'; link_to(suz,COL_CAST); suz.scale=(1.08,1.08,1.08); bpy.ops.object.transform_apply(location=False,rotation=False,scale=True); suz.data.materials.append(mat_bronze)
bpy.context.view_layer.objects.active=suz; suz.select_set(True); sub=suz.modifiers.new('sculptural smoothness','SUBSURF'); sub.levels=2; sub.render_levels=2
try: bpy.ops.object.modifier_apply(modifier=sub.name)
except: pass
set_smooth(suz); suz['graphics_history']='Suzanne, Blender mascot'; collar=add_torus('Suzanne neck ring',(3.25,.48,1.62),.66,.055,mat_gold,COL_CAST)

# -------------------------
# Checker sphere
# -------------------------
bpy.ops.mesh.primitive_uv_sphere_add(segments=64, ring_count=40, location=(-1.55,-1.38,1.68))
sphere=bpy.context.object; sphere.name='RAYTRACING CHECKER SPHERE | polished chrome'; link_to(sphere,COL_CAST); sphere.scale=(.94,.94,.94); bpy.ops.object.transform_apply(location=False,rotation=False,scale=True); sphere.data.materials.append(mat_check_dark); sphere.data.materials.append(mat_check_light)
for poly in sphere.data.polygons:
    c=poly.center.normalized() if poly.center.length else Vector((0,0,1)); theta=(math.atan2(c.y,c.x)+2*pi)%(2*pi); phi=math.acos(max(-1,min(1,c.z))); ix=int(theta/(2*pi)*12); iz=int(phi/pi*8); poly.material_index=(ix+iz)%2
set_smooth(sphere); sphere['graphics_history']='classic ray-tracing demo sphere'; sphere_ring=add_torus('Checker sphere shadow ring',(-1.55,-1.38,.77),.72,.045,mat_dark,COL_CAST)

# -------------------------
# Classical props
# -------------------------
def make_wedge(name, side, cx, cy, mat, ztop_center=1.10, ztop_outer=.98, thick=.08, width=1.12, depth=1.48, coll=COL_PROP):
    outer=cx+side*width; front=cy-depth/2; back=cy+depth/2
    verts=[(cx,front,ztop_center),(outer,front,ztop_outer),(outer,back,ztop_outer+.02),(cx,back,ztop_center+.02),(cx,front,ztop_center-thick),(outer,front,ztop_outer-thick),(outer,back,ztop_outer+.02-thick),(cx,back,ztop_center+.02-thick)]
    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)]
    o=mesh_obj(name,verts,faces,mat,coll); add_bevel(o,.018,2,True); return o
book_center=(1.35,-1.58)
make_wedge('Open book red cover left',-1,book_center[0],book_center[1],mat_red,1.065,.945,.055,1.18,1.60,COL_PROP); make_wedge('Open book red cover right',1,book_center[0],book_center[1],mat_red,1.065,.945,.055,1.18,1.60,COL_PROP); make_wedge('Open book aged pages left',-1,book_center[0],book_center[1],mat_ivory,1.14,1.01,.085,1.10,1.48,COL_PROP); make_wedge('Open book aged pages right',1,book_center[0],book_center[1],mat_ivory,1.14,1.01,.085,1.10,1.48,COL_PROP)
for side in (-1,1):
    for k in range(3):
        x0=book_center[0]+side*(.16+.25*k); x1=book_center[0]+side*(.40+.25*k); midx=(x0+x1)/2; midy=book_center[1]-.42+.22*k
        line=add_cube('Open book printed line',(midx,midy,1.115+.018*k),(.22,.012,.008),mat_page_edge,COL_PROP,bevel=.002); line.rotation_euler[2]=side*.10
bookmark=add_cube('Open book brass bookmark',(book_center[0],book_center[1]-1.0,.99),(.08,.62,.018),mat_gold,COL_PROP,bevel=.008)

cx,cy=4.75,-.35
add_cyl('Candlestick foot',(cx,cy,.79),.72,.16,mat_gold,COL_PROP,vertices=64,bevel=.045); add_torus('Candlestick foot bead',(cx,cy,.91),.54,.075,mat_gold,COL_PROP); add_cyl('Candlestick lower stem',(cx,cy,1.22),.19,.57,mat_gold,COL_PROP,vertices=48,bevel=.025); add_uv('Candlestick bulb',(cx,cy,1.58),(.34,.34,.26),mat_gold,COL_PROP,segments=40,rings=24); add_cyl('Candlestick upper stem',(cx,cy,1.92),.13,.46,mat_gold,COL_PROP,vertices=48,bevel=.018); add_torus('Candlestick drip cup',(cx,cy,2.18),.36,.07,mat_gold,COL_PROP); add_cyl('Candlestick candle',(cx,cy,2.78),.235,1.18,mat_wax,COL_PROP,vertices=48,bevel=.025); add_uv('Candle wax drip 1',(cx-.17,cy-.18,2.55),(.045,.035,.18),mat_wax,COL_PROP,segments=20,rings=12); add_uv('Candle wax drip 2',(cx+.16,cy+.06,2.68),(.04,.035,.13),mat_wax,COL_PROP,segments=20,rings=12); add_cyl('Candle wick',(cx,cy,3.39),.025,.12,mat_dark,COL_PROP,vertices=20); flame=add_uv('Candle flame',(cx,cy-.01,3.56),(.10,.085,.24),mat_flame,COL_PROP,segments=24,rings=16); flame.rotation_euler[1]=-.18

apple=add_uv('Russet apple',(-5.05,-1.18,1.32),(.64,.60,.62),mat_apple,COL_PROP,segments=48,rings=32); add_uv('Apple top dimple',(-5.05,-1.18,1.87),(.17,.17,.06),mat_dark,COL_PROP,segments=24,rings=12); add_cyl('Apple stem',(-5.02,-1.16,1.98),.045,.25,mat_dark,COL_PROP,vertices=20,bevel=.01); create_leaf('Apple leaf',[(-5.02,2.03),(-4.65,2.12),(-4.38,2.04),(-4.72,1.91)],-1.18,.035,mat_leaf,COL_PROP,.01)
pear=add_uv('Amber pear',(-4.45,.05,1.24),(.48,.45,.68),mat_orange,COL_PROP,segments=48,rings=32); add_cyl('Pear stem',(-4.45,.04,1.91),.035,.18,mat_dark,COL_PROP,vertices=20,bevel=.01)

# -------------------------
# Museum placards
# -------------------------
def plaque(name, x, y, text, width, text_size=.16):
    base=add_cube(name+' plaque',(x,y,.78),(width,.075,.23),mat_label_dark,COL_PROP,bevel=.025); face=add_cube(name+' brass face',(x,y-.045,.78),(width*.88,.018,.16),mat_label,COL_PROP,bevel=.012); add_text(name+' inscription',text,(x,y-.058,.79),text_size,mat_gold,COL_PROP,extrude=.005); return base
plaque('UTAH 1975',-.05,-1.18,'UTAH  /  1975',1.65,.145); plaque('BUNNY 1994',-3.65,-.72,'STANFORD  /  1994',1.75,.125); plaque('SUZANNE',3.25,-.78,'SUZANNE  /  BLENDER',2.00,.115); plaque('RAYTRACE',-1.55,-2.42,'RAY TRACE',1.50,.14)

# -------------------------
# Lighting and camera
# -------------------------
def point_at(obj, target):
    direction=Vector(target)-obj.location; obj.rotation_euler=direction.to_track_quat('-Z','Y').to_euler()
bpy.ops.object.light_add(type='AREA', location=(-5.2,-4.8,9.6))
key=bpy.context.object; key.name='DOMINANT WARM WINDOW SHAFT'; link_to(key,COL_LIGHT); key.data.energy=1250; key.data.shape='RECTANGLE'; key.data.size=4.5; key.data.size_y=6.5; key.data.color=(1.0,.49,.22); point_at(key,(0,.4,1.9))
scene.world.color=(0.002,0.001,0.001); scene.world.use_nodes=True; wn=scene.world.node_tree.nodes.get('Background')
if wn: wn.inputs['Color'].default_value=(0.004,0.002,0.001,1); wn.inputs['Strength'].default_value=.055
bpy.ops.object.camera_add(location=(10.4,-17.8,8.2))
cam=bpy.context.object; cam.name='Presentation Camera | Dutch still life'; link_to(cam,COL_LIGHT); cam.data.lens=52; cam.data.sensor_width=36; point_at(cam,(0.0,.45,2.35)); cam.data.dof.use_dof=True; cam.data.dof.focus_object=teapot_body; cam.data.dof.aperture_fstop=6.3; cam.data.dof.aperture_blades=7; scene.camera=cam
scene['title']='Dutch Golden Age Still Life of Computer Graphics History'; scene['required_cast']='Utah Teapot | Stanford Bunny | Suzanne | Checkered Raytracing Sphere'; scene['cornell_nod']='red velvet left, neutral grey center, green velvet right'; scene['lighting_note']='single dominant warm area light, deep umber world'
scene.render.engine='BLENDER_EEVEE'; scene.render.resolution_x=1536; scene.render.resolution_y=1536; scene.render.resolution_percentage=100; scene.render.image_settings.file_format='PNG'
bpy.context.view_layer.objects.active=cam
for o in bpy.context.selected_objects: o.select_set(False)
cam.select_set(True)
path='/tmp/dutch_graphics_still_life.blend'; bpy.ops.wm.save_as_mainfile(filepath=path)
print('Created objects:',len(bpy.data.objects),'materials:',len(bpy.data.materials),'saved',path)
result={'objects':len(bpy.data.objects),'materials':len(bpy.data.materials),'path':path,'camera':cam.name}


Traceback (most recent call last):
  File "<repo>\.runner\blender-mcp-v1.0.0\addon\mcp_to_blender_server.py", line 230, in _execute_code
    exec(code, namespace)
    ~~~~^^^^^^^^^^^^^^^^^
  File "<string>", line 360, in <module>
RuntimeError: Error: Cannot open file <repo>\apps\console\tmp\dutch_graphics_still_life.blend@ for writing: No such file or directory