```python
import bpy, math, random, os
from mathutils import Vector

random.seed(24)
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# A small, carefully maintained shrine on an otherwise weathered appliance.
def material(name, color, rough=.5, metal=0, emission=None):
    m = bpy.data.materials.new(name)
    m.diffuse_color = (*color, 1)
    m.use_nodes = True
    p = m.node_tree.nodes.get('Principled BSDF')
    p.inputs['Base Color'].default_value = (*color, 1)
    p.inputs['Roughness'].default_value = rough
    p.inputs['Metallic'].default_value = metal
    if emission:
        p.inputs['Emission Color'].default_value = (*color, 1)
        p.inputs['Emission Strength'].default_value = emission
    return m

def textured(name, a, b, scale, rough=.6, metal=0):
    m = material(name, a, rough, metal)
    n, l = m.node_tree.nodes, m.node_tree.links
    p = n.get('Principled BSDF')
    t = n.new('ShaderNodeTexNoise')
    t.inputs['Scale'].default_value = scale
    t.inputs['Detail'].default_value = 3
    ramp = n.new('ShaderNodeValToRGB')
    ramp.color_ramp.elements[0].position = .22
    ramp.color_ramp.elements[0].color = (*a, 1)
    ramp.color_ramp.elements[1].position = .8
    ramp.color_ramp.elements[1].color = (*b, 1)
    l.new(t.outputs['Fac'], ramp.inputs[0])
    l.new(ramp.outputs[0], p.inputs['Base Color'])
    bump = n.new('ShaderNodeBump')
    bump.inputs['Strength'].default_value = .13
    bump.inputs['Distance'].default_value = .012
    l.new(t.outputs['Fac'], bump.inputs['Height'])
    l.new(bump.outputs['Normal'], p.inputs['Normal'])
    return m

red = textured('Vermilion enamel, sun-softened', (.34,.035,.019), (.73,.13,.065), 5, .4, .22)
cream = textured('Warm ivory enamel', (.66,.65,.51), (.9,.87,.7), 22, .36, .13)
wood = textured('New oiled cedar', (.25,.085,.025), (.57,.29,.09), 8, .44)
endwood = material('Cedar end grain', (.67,.38,.15))
darkwood = material('Dark timber joints', (.14,.047,.018))
metal = material('Brushed stainless steel', (.38,.45,.47), .28, .8)
dark = material('Deep recesses', (.021,.032,.036), .65)
rubber = material('Window gasket', (.048,.059,.051), .55)
rust = textured('Old oxide', (.13,.033,.013), (.42,.14,.035), 28, .91)
chip = material('Exposed primer', (.27,.24,.18), .8)
straw = material('Golden rice straw', (.66,.45,.18), .8)
straw_light = material('Fresh straw highlights', (.86,.68,.33), .72)
paper = material('Folded ritual paper', (.96,.93,.79), .73)
ceramic = material('Ivory glazed porcelain', (.9,.92,.83), .2)
bib = material('Fox votive red', (.7,.028,.025), .46)
black = material('Ink and fox eyes', (.014,.022,.025), .32)
brass = material('Polished offering bell', (.68,.40,.09), .23, .78)
warm = material('Warm fluorescent diffuser', (1,.73,.35), .33, emission=5)
backlight = material('Luminous display backing', (.79,.74,.50), .55, emission=.6)
buttonmat = material('Pale jade button light', (.41,.87,.65), .23, emission=2.4)
buttonrim = material('Button chrome', (.46,.56,.54), .22, .75)
asphalt = textured('Fine fractured asphalt', (.035,.044,.054), (.105,.115,.121), 75, .92)
concrete = textured('Old pale concrete', (.28,.29,.27), (.49,.49,.41), 24, .85)
soil = textured('Mossy road shoulder', (.08,.10,.057), (.23,.25,.12), 8, .95)
grassmats = [material('Grass '+str(i), c, .8) for i,c in enumerate(
    [(.16,.23,.09),(.27,.32,.12),(.38,.38,.15),(.10,.19,.13)])]
crackmat = material('Dark asphalt fissures', (.012,.016,.022), 1)
whitepaint = material('Worn road edge paint', (.64,.63,.44), .87)
ghost = material('Old adhesive rectangle', (.63,.62,.47), .8)

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

def bevel(obj, width=.015, seg=3):
    mod = obj.modifiers.new('Soft manufactured edges', 'BEVEL')
    mod.width, mod.segments = width, seg
    mod = obj.modifiers.new('Weighted corner normals', 'WEIGHTED_NORMAL')
    return obj

def box(name, loc, size, mat, bevelw=0):
    bpy.ops.mesh.primitive_cube_add(size=1, location=loc)
    o=bpy.context.object; o.name=name
    o.dimensions=size
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    assign(o,mat)
    if bevelw: bevel(o,bevelw)
    return o

def uv(name, loc, scale, mat, seg=32, rings=20):
    bpy.ops.mesh.primitive_uv_sphere_add(segments=seg, ring_count=rings, location=loc)
    o=bpy.context.object; o.name=name; o.scale=scale; assign(o,mat)
    for f in o.data.polygons: f.use_smooth=True
    return o

def cyl(name, loc, radius, depth, mat, vertices=48, rotation=None):
    bpy.ops.mesh.primitive_cylinder_add(vertices=vertices, radius=radius, depth=depth, location=loc)
    o=bpy.context.object; o.name=name
    if rotation: o.rotation_euler=rotation
    assign(o,mat)
    bevel(o,min(.003, radius*.13),2)
    for f in o.data.polygons: f.use_smooth=True
    return o

def rod(name, a, b, r, mat, verts=16):
    a,b=Vector(a),Vector(b)
    o=cyl(name,(a+b)/2,r,(b-a).length,mat,verts)
    o.rotation_euler=(b-a).to_track_quat('Z','Y').to_euler()
    return o

def mesh(name, verts, faces, mat):
    me=bpy.data.meshes.new(name); me.from_pydata(verts,[],faces); me.update()
    o=bpy.data.objects.new(name,me); bpy.context.collection.objects.link(o); assign(o,mat)
    return o

def tube(name, points, radius, mat, sides=8, radii=None):
    pts=[Vector(p) for p in points]
    verts=[]
    for i,p in enumerate(pts):
        tangent=pts[min(i+1,len(pts)-1)]-pts[max(0,i-1)]
        tangent.normalize()
        ref=Vector((0,0,1))
        if abs(tangent.dot(ref))>.95: ref=Vector((0,1,0))
        u=tangent.cross(ref).normalized(); v=tangent.cross(u).normalized()
        r=radius if radii is None else radii[i]
        for j in range(sides):
            verts.append(tuple(p+r*(u*math.cos(j*2*math.pi/sides)+v*math.sin(j*2*math.pi/sides))))
    faces=[]
    for i in range(len(pts)-1):
        for j in range(sides):
            a=i*sides+j; b=i*sides+(j+1)%sides
            faces.append((a,b,b+sides,a+sides))
    faces += [tuple(range(sides-1,-1,-1)),tuple((len(pts)-1)*sides+j for j in range(sides))]
    o=mesh(name,verts,faces,mat)
    for f in o.data.polygons: f.use_smooth=True
    return o

def text(name, body, loc, size, mat, rotation=(math.pi/2,0,0), align='CENTER'):
    cu=bpy.data.curves.new(name,'FONT')
    cu.body=body; cu.align_x=align; cu.size=size
    cu.extrude=.00035; cu.bevel_depth=.00012
    ob=bpy.data.objects.new(name,cu); bpy.context.collection.objects.link(ob)
    ob.location=loc; ob.rotation_euler=rotation; assign(ob,mat)
    return ob

def bolt(loc, side=False):
    rot=(0,math.pi/2,0) if side else (math.pi/2,0,0)
    cyl('Oxidised washer',loc,.011,.0025,rust,24,rot)
    q=list(loc); q[0 if side else 1]-=.002
    cyl('Hex cabinet fastener',q,.006,.005,metal,6,rot)

# Irregular, finite roadside diorama; the back edge rises into the mountain shoulder.
outline=[(-1.62,-1.18),(-1.22,-1.43),(.25,-1.48),(1.46,-1.2),(1.62,-.65),
         (1.49,.64),(.92,.97),(-.59,1.0),(-1.58,.68),(-1.77,-.30)]
v=[(x,y,z) for z in (-.09,.02) for x,y in outline]
N=len(outline)
faces=[tuple(range(N-1,-1,-1)),tuple(range(N,2*N))]
faces += [(i,(i+1)%N,(i+1)%N+N,i+N) for i in range(N)]
bevel(mesh('Island of mountain road',v,faces,asphalt),.035,3)
box('Earthen verge', (0,.74,.045),(2.79,.50,.16),soil,.12)
for i in range(7):
    b=box('Individual curb block',(-1.32+i*.44,.47,.075),(.425,.19,.18),concrete,.015)
    b.rotation_euler.z=random.uniform(-.018,.018)
for i in range(14):
    x=-1.45+i*.21
    box('Broken road edge marking',(x,-.87,.026),(.17+random.random()*.035,.067,.0018),whitepaint,.002)
for k in range(15):
    x=random.uniform(-1.55,1.35); y=random.uniform(-1.3,.31)
    if abs(x)<.57 and y>-.43: continue
    pts=[(x,y,.026)]
    for j in range(random.randint(3,7)):
        x+=random.uniform(-.14,.14); y+=random.uniform(.05,.16)
        pts.append((x,y,.027))
    tube('Hairline road fracture',pts,.0025,crackmat,5)
    if k%2==0:
        p=pts[len(pts)//2]
        tube('Branching fracture',[p,(p[0]+.13,p[1]+.07,.027),(p[0]+.21,p[1]+.06,.027)],.0017,crackmat,5)

for i in range(35):
    x=random.uniform(-1.38,1.4); y=random.uniform(.59,.91)
    if i<12:
        x=random.choice([-.59,.59])+random.uniform(-.1,.1); y=random.uniform(-.32,.45)
    for j in range(random.randint(4,8)):
        a=random.random()*math.tau; h=random.uniform(.065,.18)
        dx,dy=math.cos(a),math.sin(a)
        width=random.uniform(.007,.016)
        z=.06 if y>.58 else .035
        mesh('Bent wild grass blade',
             [(x-width*dy,y+width*dx,z),(x+width*dy,y-width*dx,z),
              (x+dx*.03+width*.3,y+dy*.03,z+h*.60),
              (x+dx*.065,y+dy*.065,z+h)],
             [(0,1,2,3)],random.choice(grassmats))
for i in range(12):
    uv('Shoulder pebble',(random.uniform(-1.4,1.4),random.uniform(.61,.91),.13),
       (random.uniform(.035,.075),.035,.025),concrete,12,8)

# The cabinet is assembled around a real recessed display, not a picture on a box.
for x in [-.37,.37]:
    for y in [-.25,.25]:
        box('Leveling foot',(x,y,.105),(.14,.15,.17),dark,.015)
box('Rear sheet-metal cabinet',(0,.135,1.065),(1,.48,1.83),red,.035)
box('Left rolled cabinet edge',(-.468,-.135,1.065),(.064,.49,1.83),red,.023)
box('Right rolled cabinet edge',(.468,-.135,1.065),(.064,.49,1.83),red,.023)
box('Chamfered roof',(0,0,1.947),(1.035,.79,.087),red,.033)
box('Roof rain lip',(0,-.407,1.921),(1.04,.046,.036),metal,.009)
box('Bottom kick plate',(0,-.388,.216),(.91,.05,.12),red,.012)
box('Lower ivory front door',(0,-.395,.705),(.896,.048,.882),cream,.013)
box('Display left stile',(-.425,-.397,1.505),(.049,.05,.71),cream,.009)
box('Display right stile',(.425,-.397,1.505),(.049,.05,.71),cream,.009)
box('Upper header',(0,-.4,1.845),(.897,.055,.087),cream,.008)
box('Window rubber surround',(0,-.374,1.478),(.838,.073,.68),rubber,.013)
box('Window luminous back',(0,-.415,1.478),(.786,.012,.626),backlight,.006)
# Backing is behind all products; sample cans project toward the inset glazing.
# Products have a flat front label region, rolled seams, pull tabs, and small printed legends.
labels=[material('Can label '+str(i),c,.33,.26) for i,c in enumerate(
    [(.8,.16,.055),(.15,.46,.35),(.84,.65,.17),(.16,.38,.59),(.72,.72,.59),(.47,.24,.49)])]
names=['MIKAN','TEA','YUZU','SODA','COFFEE','UME']
for row in range(2):
    zbase=1.245+row*.255
    shelf=box('Slanted sample shelf',(0,-.441,zbase),(.79,.155,.017),metal,.004)
    shelf.rotation_euler.x=math.radians(-9)
    box('Illuminated shelf rail',(0,-.51,zbase-.012),(.795,.023,.028),cream,.004)
    for j in range(6):
        x=-.329+j*.132
        z=zbase+.111
        idx=(j+row*2)%6
        cyl('Drink sample %02d'%(row*6+j+1),(x,-.445,z),.048,.177,labels[idx],64)
        cyl('Can rolled bottom',(x,-.445,z-.087),.049,.006,metal,48)
        cyl('Can silver lid',(x,-.445,z+.088),.048,.006,metal,48)
        cyl('Can lower label band',(x,-.445,z-.051),.0486,.013,paper,48)
        cyl('Can upper label band',(x,-.445,z+.051),.0486,.009,paper,48)
        uv('Can pull tab',(x,-.449,z+.093),(.013,.022,.002),metal,20,10)
        uv('Fruit medallion',(x,-.493,z+.006),(.021,.002,.024),paper,24,12)
        text('Can label',names[idx],(x,-.496,z-.035),.011,black)
        text('Sample number',str(row*6+j+1).zfill(2),(x,-.525,zbase-.021),.014,black)
box('Top fluorescent tube',(0,-.463,1.779),(.77,.046,.024),warm,.01)
for x in [-.387,.387]:
    box('Vertical reflector',(x,-.449,1.504),(.012,.06,.53),metal,.003)

glass=material('Very lightly smoked display glazing',(.53,.74,.69),.15,.08)
p=glass.node_tree.nodes.get('Principled BSDF')
p.inputs['Alpha'].default_value=.095
p.inputs['Transmission Weight'].default_value=.25
glass.diffuse_color=(.53,.74,.69,.095)
try: glass.surface_render_method='DITHERED'
except: pass
box('Single slightly tinted protective glass',(0,-.517,1.505),(.801,.003,.584),glass,.003)
# Narrow reflections make the glazing legible without obscuring the cans.
glint=material('Soft glazing reflected edge',(.51,.70,.75),.28,emission=.12)
for x in [-.38,.378]:
    box('Glass edge reflection',(x,-.520,1.49),(.002,.001,.51),glint)

text('Machine maker','KITSUNE',(0,-.432,1.832),.035,red)
text('Maker subtitle','J I D O H A N B A I K I',(0,-.434,1.798),.011,black)
# Twelve choices in a clearly organized six by two grid.
box('Selection band',(-.083,-.433,1.018),(.682,.032,.201),metal,.012)
for row in range(2):
    for j in range(6):
        x=-.357+j*.11; z=1.065-row*.102
        box('Price label',(x,-.459,z-.035),(.079,.006,.022),paper,.003)
        text('Price',('OFFER' if (row==1 and j==5) else '¥120'),
             (x,-.464,z-.041),.013,black)
        if row==1 and j==5: continue
        box('Button dark socket',(x,-.458,z),(.087,.018,.038),dark,.012)
        box('Selection chrome bezel',(x,-.47,z),(.079,.016,.032),buttonrim,.012)
        box('Illuminated choice button',(x,-.482,z+.001),(.066,.012,.021),buttonmat,.009)
        text('Button number',str(row*6+j+1).zfill(2),(x,-.49,z-.005),.012,black)

# Coin and note hardware down the right edge.
box('Payment service inset',(.346,-.439,1.01),(.137,.045,.225),dark,.009)
box('Coin escutcheon',(.346,-.466,1.072),(.11,.012,.073),metal,.009)
box('Vertical coin slot',(.334,-.474,1.078),(.009,.008,.043),black,.004)
cyl('Return button',(.378,-.48,1.076),.012,.012,brass,24,(math.pi/2,0,0))
box('Bill acceptor',(.346,-.473,.993),(.113,.025,.042),metal,.004)
box('Banknote slit',(.346,-.489,.995),(.096,.004,.009),black,.002)
box('Banknote green indicator',(.346,-.491,1.015),(.061,.002,.004),buttonmat,.001)
text('Payment legend','1000 YEN',(.346,-.472,.944),.012,paper)
box('Coin return pocket',(.352,-.44,.805),(.105,.026,.071),metal,.01)
box('Coin return dark opening',(.352,-.458,.811),(.081,.009,.039),dark,.008)

# Lower flap hatch, hinged at its top edge.
box('Dispensing recess',(-.143,-.433,.547),(.518,.036,.271),rubber,.019)
box('Stainless delivery surround',(-.143,-.459,.547),(.493,.025,.241),metal,.012)
box('Dark delivery throat',(-.143,-.475,.535),(.445,.009,.177),dark,.009)
door=box('Hinged metal push flap',(-.143,-.489,.552),(.428,.016,.165),metal,.008)
door.rotation_euler.x=math.radians(-7)
rod('Full width delivery hinge',(-.365,-.495,.642),(.08,-.495,.642),.009,metal,24)
text('Push door inscription','P U S H',(-.143,-.507,.536),.029,black)
text('Delivery label','COLD DRINKS  /  WARM WISHES',(-.143,-.447,.712),.018,red)
for x in [-.406,.406]:
    for z in [.30,.77,1.16,1.85]: bolt((x,-.435,z))
for z in [.43,.55,.67]:
    box('Front air louver',(-.15,-.427,z-.20),(.26,.015,.012),dark,.004)
cyl('Door service lock',(.415,-.443,.849),.014,.01,metal,32,(math.pi/2,0,0))
box('Lock keyway',(.415,-.451,.849),(.002,.004,.013),black)
# Side vents and service seam.
for z in [.36+i*.026 for i in range(7)]:
    box('Left ventilation slit',(-.505,.095,z),(.009,.32,.01),dark,.003)
for y in [-.22,.27]:
    for z in [.28,1.71]: bolt((-.507,y,z),True)
box('Left side service seam',(-.502,.302,1.025),(.004,.005,1.45),dark,.001)

# Wear is actual small geometry as well as the enamel shader.
def patch_front(name,x,y,z,rx,rz,mat):
    pts=[]
    for k in range(13):
        a=k*math.tau/13; f=random.uniform(.6,1.15)
        pts.append((x+rx*f*math.cos(a),y,z+rz*f*math.sin(a)))
    return mesh(name,pts,[tuple(range(13))],mat)
for i in range(48):
    x=random.uniform(-.45,.45); z=random.uniform(.168,.269)
    patch_front('Chipped lower enamel',x,-.424,z,random.uniform(.004,.025),random.uniform(.003,.019),rust)
for i in range(15):
    x=random.uniform(-.47,.47)
    if abs(x)<.39: continue
    z=random.uniform(1.63,1.86)
    patch_front('Rain-washed iron trail',x,-.431,z,.002,random.uniform(.018,.08),rust)
# Large peeled-sticker ghost with just a few surviving scraps.
box('Peeled sticker ghost',(-.252,-.424,.814),(.217,.001,.077),ghost,.004)
for x in [-.36,-.145]:
    box('Old sticker adhesive edge',(x,-.426,.814),(.003,.001,.071),chip)
for z in [.777,.851]:
    box('Old sticker adhesive edge',(-.253,-.426,z),(.217,.001,.002),chip)
for i in range(7):
    patch_front('Sticker paper remnant',random.uniform(-.36,-.145),-.428,
                random.choice([.780,.846]),.013,.003,paper)

# New slatted saisen-bako attached BESIDE the hatch.
box('Offering box shadow',(.30,-.51,.527),(.277,.13,.23),darkwood,.01)
box('Offering box cedar body',(.30,-.585,.51),(.264,.263,.205),wood,.009)
for x in [.168,.432]:
    box('Offering box corner stile',(x,-.724,.512),(.023,.023,.216),endwood,.003)
box('Offering box bottom foot',(.30,-.591,.402),(.29,.285,.027),darkwood,.005)
box('Black slot under slatted lid',(.30,-.587,.619),(.235,.229,.013),dark,.002)
for j in range(6):
    box('Offering box individual top slat',(.19+j*.044,-.587,.633),(.027,.259,.018),wood,.003)
for y in [-.728,-.446]:
    box('Offering box rim',(.30,y,.638),(.299,.023,.035),endwood,.004)
text('Offering inscription','SAISEN',(.30,-.721,.530),.034,darkwood)
text('Offering subtitle','gratitude',(.30,-.722,.482),.018,darkwood)
for x in [.192,.408]: bolt((x,-.73,.443))

# Bell substitutes exactly one selection button.
bellx=.193
box('Bell mounting plate',(bellx,-.472,.969),(.069,.018,.064),brass,.007)
rod('Bell bracket',(bellx,-.482,1.003),(bellx,-.557,1.003),.009,brass)
def lathe(name,center,profile,mat,n=48):
    verts=[]
    for r,z in profile:
        for j in range(n):
            a=j*math.tau/n
            verts.append((center[0]+r*math.cos(a),center[1]+r*math.sin(a),center[2]+z))
    faces=[]
    for i in range(len(profile)-1):
        for j in range(n):
            a=i*n+j;b=i*n+(j+1)%n
            faces.append((a,b,b+n,a+n))
    o=mesh(name,verts,faces,mat)
    for p in o.data.polygons:p.use_smooth=True
    return o
lathe('Tiny brass devotional bell',(bellx,-.558,.932),
      [(.032,0),(.034,.005),(.023,.016),(.017,.037),(.008,.052),(.003,.055)],brass)
uv('Bell clapper',(bellx,-.558,.931),(.007,.007,.012),darkwood,20,12)
for s,mat in enumerate([paper,bib]):
    pts=[]
    for i in range(75):
        t=i/74; a=t*math.tau*7+s*math.pi
        pts.append((bellx+.005*math.cos(a),-.560+.005*math.sin(a),.932-.186*t))
    tube('Red and white bell pull',pts,.0038,mat,7)
uv('Bell rope end knot',(bellx,-.560,.746),(.010,.009,.013),bib,20,12)
for i in range(6):
    tube('Bell tassel',[(bellx+(i-2.5)*.003,-.56,.743),
                       (bellx+(i-2.5)*.004,-.56,.716)],.0018,paper,5)

# Shimenawa, three genuinely helical straw bundles.
def rope_center(t):
    return Vector((-.448+.896*t,-.564,1.844-.075*math.sin(math.pi*t)))
for strand in range(3):
    points=[]
    for i in range(225):
        t=i/224; a=t*math.tau*15+strand*math.tau/3
        c=rope_center(t)
        points.append((c.x,c.y+.012*math.cos(a),c.z+.012*math.sin(a)))
    tube('Twisted shimenawa straw bundle',points,.011,straw if strand%2 else straw_light,9)
    for filament in range(2):
        fine=[]
        for i,p in enumerate(points):
            a=i/224*math.tau*42+filament*math.pi
            fine.append((p[0],p[1]+.0105*math.cos(a),p[2]+.0105*math.sin(a)))
        tube('Fine rice straw fibre',fine,.0011,straw_light,5)
for x in [-.448,.448]:
    for k in range(12):
        a=random.uniform(-1,1)
        tube('Bound rope cut end',[(x,-.564,1.844),(x+a*.02,-.56+random.uniform(-.017,.017),1.795)],
             .0019,straw_light,5)

# Cut zigzag polygons; alternating fold panels catch the warm light.
for idx,t in enumerate([.19,.50,.81]):
    c=rope_center(t)
    x,y,z=c.x,-.589,c.z-.012
    outline2=[(-.018,0),(.022,0),(.022,-.038),(.047,-.038),(.047,-.079),
              (.022,-.079),(.022,-.113),(.043,-.113),(.043,-.148),
              (.008,-.148),(.008,-.111),(-.013,-.111),(-.013,-.069),
              (.011,-.069),(.011,-.032),(-.018,-.032)]
    verts=[(x+a,y+(i%3)*.002,z+b) for i,(a,b) in enumerate(outline2)]
    o=mesh('Shide lightning streamer '+str(idx+1),verts,[tuple(range(len(verts)))],paper)
    so=o.modifiers.new('Paper thickness','SOLIDIFY');so.thickness=.0014
    rod('Shide fold', (x+.012,y-.001,z-.070),(x+.044,y-.001,z-.078),.0008,ghost,6)

# Side rack, visible on the left in the presentation.
for y in [-.245,.265]:
    rod('Omikuji rack upright',(-.562,y,.89),(-.562,y,1.59),.006,metal)
    for z in [.92,1.57]:
        rod('Rack spacer',(-.５０ if False else -.506,y,z),(-.562,y,z),.005,metal)
for z in [1.01,1.22,1.43,1.57]:
    rod('Fortune tying wire',(-.565,-.25,z),(-.565,.27,z),.003,metal,12)

for i in range(18):
    y=random.uniform(-.23,.245)
    z=random.choice([1.01,1.22,1.43,1.57])+random.uniform(-.009,.009)
    x=-.576-random.uniform(0,.016)
    uv('Hand tied fortune knot',(x,y,z),(.014,.022,.011),paper,16,10)
    angle=random.uniform(-.6,.6)
    length=random.uniform(.09,.20)
    for sign in [-1,1]:
        verts=[]
        for k in range(5):
            t=k/4
            yy=y+math.sin(angle)*length*t+sign*.012
            zz=z-length*t*(1 if sign==1 else -.38)
            xx=x-.009-math.sin(t*math.pi*3+i)*.012
            verts.extend([(xx,yy-.010,zz),(xx,yy+.010,zz+.004)])
        ob=mesh('Folded omikuji paper tail',verts,[(k*2,k*2+1,k*2+3,k*2+2) for k in range(4)],paper)
        so=ob.modifiers.new('Fortune paper thickness','SOLIDIFY');so.thickness=.001
        if sign==1:
            for k in range(3):
                tt=.25+k*.15
                zz=z-length*tt
                yy=y+math.sin(angle)*length*tt
                rod('Fortune printed ink', (x-.025,yy-.005,zz),(x-.025,yy+.005,zz+.003),.0007,black,5)
text('Side shrine notice','FORTUNES',(-.587,.19,1.68),.036,paper,(math.pi/2,0,-math.pi/2))
text('Side small notice','take hope / leave thanks',(-.59,.23,.84),.018,paper,(math.pi/2,0,-math.pi/2))

# Roof torii: jointed cedar posts, lower tie beam, curved kasagi lintel.
for x in [-.388,.388]:
    box('Torii mounting shoe',(x,0,2.003),(.139,.18,.032),metal,.005)
    for yy in [-.061,.061]:
        cyl('Roof mounting bolt',(x,yy,2.026),.009,.014,metal,6)
    rod('Torii tapered-looking cedar pillar',(x,0,2.02),(x*.93,0,2.497),.035,wood,32)
    box('Torii lower post collar',(x,0,2.052),(.084,.084,.04),darkwood,.006)
box('Torii nuki cross tie',(0,0,2.387),(.944,.083,.059),wood,.005)
for x in [-.362,.362]:
    box('Torii proud through joint',(x,-.05,2.387),(.061,.025,.045),endwood,.003)
box('Torii central gakuzuka',(0,0,2.456),(.062,.068,.114),wood,.004)
# Curved top beam, made as a solid mesh with upward swept ends.
xs=[-.56,-.48,-.36,-.20,0,.20,.36,.48,.56]
zs=[2.574,2.553,2.530,2.518,2.515,2.518,2.530,2.553,2.574]
verts=[]
for x,z in zip(xs,zs):
    verts += [(x,-.075,z-.055),(x,.075,z-.055),(x,.075,z+.015),(x,-.075,z+.015)]
faces=[(0,3,2,1)]
for i in range(len(xs)-1):
    for k in range(4):
        faces.append((4*i+k,4*i+(k+1)%4,4*(i+1)+(k+1)%4,4*(i+1)+k))
faces.append(tuple(range(4*(len(xs)-1),4*len(xs))))
bevel(mesh('Upswept torii kasagi',verts,faces,wood),.009,3)
tube('Dark crown of torii',[(x,-.003,z+.017) for x,z in zip(xs,zs)],.022,darkwood,8)
box('Shrine dedication tablet',(0,-.063,2.453),(.101,.015,.106),darkwood,.006)
text('Torii dedication','INARI',(0,-.074,2.454),.021,paper)
text('Torii date','1998',(0,-.074,2.430),.013,endwood)

# Two porcelain kitsune, mirror posed toward a shared offering.
box('Fox offering tray',(0,-.10,2.007),(.63,.34,.028),wood,.013)
def fox(name,cx,sgn):
    # Model in local coordinates: +X is the direction the fox faces.
    def P(x,y,z): return (cx+sgn*x,y-.10,z+2.025)
    uv(name+' seated haunch',P(-.038,0,.089),(.070,.054,.087),ceramic)
    uv(name+' upright chest',P(.004,-.006,.136),(.043,.043,.084),ceramic)
    for yy in [-.033,.034]:
        uv(name+' front leg',P(.038,yy,.052),(.015,.016,.052),ceramic,24,16)
        uv(name+' little paw',P(.053,yy,.014),(.029,.018,.014),ceramic,24,16)
    uv(name+' alert head',P(.031,0,.215),(.052,.044,.049),ceramic)
    uv(name+' tapered muzzle',P(.073,-.002,.203),(.040,.024,.023),ceramic,28,16)
    uv(name+' nose',P(.107,-.002,.208),(.009,.013,.010),black,20,12)
    for yy in [-.030,.030]:
        # Tall triangular ears with inset red interiors.
        ev=[P(.006,yy-.014,.235),P(.047,yy-.011,.238),P(.015,yy,.305),
            P(.006,yy+.014,.235),P(.047,yy+.011,.238)]
        bevel(mesh(name+' pointed ear',ev,[(0,1,2),(3,2,4),(0,3,4,1),(0,2,3),(1,4,2)],ceramic),.003,2)
        mesh(name+' red inner ear',[P(.013,yy-.0145,.242),P(.038,yy-.012,.243),P(.016,yy-.003,.287)],[(0,1,2)],bib)
        uv(name+' black eye',P(.061,yy*1.20,.225),(.006,.004,.007),black,20,12)
        tube(name+' red brow',[P(.044,yy*1.25,.239),P(.06,yy*1.27,.241),P(.071,yy*1.18,.235)],.002,bib,6)
    # Broad curled tail rises behind the seated body.
    tail=[P(-.068,.020,.045),P(-.099,.020,.074),P(-.117,.018,.116),
          P(-.117,.012,.153),P(-.095,.006,.179),P(-.074,.002,.193)]
    tube(name+' sweeping pointed tail',tail,.03,ceramic,16,[.031,.036,.032,.027,.017,.002])
    # Votive bib wraps the neck and forms a bright triangular chest scarf.
    tube(name+' red neck ribbon',[P(.017+.039*math.cos(a),.043*math.sin(a),.174)
         for a in [i*math.tau/40 for i in range(41)]],.006,bib,8)
    verts=[P(.040,-.041,.173),P(.055,.034,.173),P(.067,.001,.112)]
    ob=mesh(name+' triangular votive bib',verts,[(0,1,2)],bib)
    so=ob.modifiers.new('Cloth thickness','SOLIDIFY');so.thickness=.003
    uv(name+' bib gilded bead',P(.068,0,.128),(.005,.007,.006),brass,16,10)
fox('Left guardian',-.205,1)
fox('Right guardian',.205,-1)
cyl('Shared ceramic offering saucer',(0,-.15,2.035),.058,.012,ceramic,48)
uv('Rice offering',(0,-.15,2.050),(.039,.037,.022),paper,24,16)
for x in [-.31,.31]:
    box('Upward roof shrine light',(x,-.307,2.006),(.072,.055,.017),darkwood,.004)
    box('Roof light diffuser',(x,-.307,2.016),(.059,.041,.004),warm,.003)

# Single nobori with modeled cloth and hand-drawn devotional calligraphy.
polex=1.00; poley=.15
cyl('Banner heavy stone socket',(polex,poley,.104),.13,.18,concrete,32)
rod('Banner bamboo pole',(polex,poley,.17),(polex,poley,2.32),.013,wood,24)
uv('Pole finial',(polex,poley,2.335),(.024,.024,.03),brass,24,16)
rod('Nobori cross arm',(.965,poley,2.275),(1.415,poley,2.275),.009,wood,20)
flagmat=material('Nobori natural cotton',(.83,.79,.60),.86)
verts=[]; nx=10;nz=32
for iz in range(nz+1):
    t=iz/nz
    for ix in range(nx+1):
        u=ix/nx
        verts.append((1.035+u*.345,poley-.018-.029*math.sin(u*math.pi*2+t*8)*u,
                      2.248-t*1.43+.016*math.sin(u*math.pi)*math.sin(t*8)))
faces=[]
for iz in range(nz):
    for ix in range(nx):
        a=iz*(nx+1)+ix
        faces.append((a,a+1,a+nx+2,a+nx+1))
flag=mesh('Single flowing nobori banner',verts,faces,flagmat)
so=flag.modifiers.new('Woven cloth thickness','SOLIDIFY');so.thickness=.002
for z in [2.23,1.91,1.58,1.24,.87]:
    tube('Banner cloth pole tie',[(.994,.15,z),(1.024,.118,z+.007),(1.042,.13,z)],.006,paper,8)
tube('Banner vermilion left hem',[(1.046,poley-.025,2.244),(1.046,poley-.025,.825)],.006,bib,6)
tube('Banner vermilion right hem',[(1.37,poley-.05,2.244),(1.37,poley-.05,.825)],.006,bib,6)
text('Banner heading','INARI',(1.205,.095,2.133),.051,red)
text('Banner footer','DRINKS',(1.205,.077,.999),.040,red)
text('Banner footer small','& BLESSINGS',(1.205,.075,.941),.022,red)

# Two large, recognizable hand-lettered kanji: 奉納, “dedicated offering”.
def glyph(strokes,cx,cz,s):
    for stroke in strokes:
        pts=[(cx+x*s,.069,cz+y*s) for x,y in stroke]
        tube('Nobori hand-painted calligraphy',pts,.007,red,7)
hou=[
 [(-.40,.35),(.36,.35)],[(-.32,.18),(.30,.18)],[(-.44,.01),(.43,.01)],
 [(.00,.51),(-.05,.12),(-.20,-.07),(-.44,-.22)],
 [(.12,.15),(.24,-.06),(.46,-.19)],
 [(-.25,-.19),(.25,-.19)],[(-.29,-.34),(.29,-.34)],
 [(0,-.12),(0,-.56)]]
nou=[
 [(-.30,.48),(-.45,.21),(-.26,.22),(-.43,-.02),(-.16,.02)],
 [(-.26,.13),(-.13,-.06)],[(-.29,-.03),(-.29,-.51)],
 [(-.41,-.13),(-.48,-.41)],[(-.17,-.13),(-.11,-.35)],
 [(0,-.49),(0,.27),(.42,.27),(.42,-.49),(.29,-.44)],
 [(.20,.48),(.20,.06),(.12,-.14),(.04,-.22)],
 [(.21,.04),(.34,-.20)]]
glyph(hou,1.208,1.880,.265)
glyph(nou,1.208,1.470,.265)
# Small fox symbol near banner foot.
mesh('Banner fox emblem',[(1.165,.065,1.141),(1.168,.065,1.220),(1.20,.065,1.193),
                         (1.24,.065,1.220),(1.24,.065,1.141),(1.203,.065,1.115)],
     [(0,1,2,3,4,5)],red)

# A few cared-for touches, including a tiny fresh flower by the offering box.
cyl('Small ceramic flower vessel',(.588,-.32,.17),.045,.22,ceramic,40)
for i in range(3):
    x=.588+random.uniform(-.026,.026); y=-.32+random.uniform(-.025,.025); z=.39+i*.025
    rod('Flower stem',(.588,-.32,.26),(x,y,z),.0025,grassmats[0],8)
    for j in range(5):
        a=j*math.tau/5
        uv('Little white chrysanthemum petal',(x+.019*math.cos(a),y+.019*math.sin(a),z),
           (.014,.009,.007),paper,16,8)
    uv('Flower golden center',(x,y,z+.006),(.009,.009,.006),straw_light,16,8)

# Lighting: warm appliance first, cool mountain dusk second.
world=bpy.data.worlds.new('Blue hour mountain air')
bpy.context.scene.world=world; world.use_nodes=True
world.node_tree.nodes['Background'].inputs[0].default_value=(.105,.17,.29,1)
world.node_tree.nodes['Background'].inputs[1].default_value=.30
def area(name,loc,target,power,color,size,size_y=None):
    data=bpy.data.lights.new(name,'AREA');data.energy=power;data.color=color
    if size_y:
        data.shape='RECTANGLE';data.size=size;data.size_y=size_y
    else:data.shape='DISK';data.size=size
    o=bpy.data.objects.new(name,data);bpy.context.collection.objects.link(o);o.location=loc
    o.rotation_euler=(Vector(target)-Vector(loc)).to_track_quat('-Z','Y').to_euler()
    return o
area('Warm fluorescent display spill',(0,-.58,1.55),(0,-1.6,.22),28,(1,.66,.31),.75,.40)
area('Soft display frontal spill',(0,-.60,1.45),(0,-2.4,1.0),12,(1,.77,.48),.7,.45)
area('Cool open dusk sky',(-3,-2,5),(0,0,1),160,(.42,.60,1),4)
area('Blue ridge rim',(2.3,2.5,3.5),(0,0,1.2),140,(.35,.49,.84),3)
area('Subtle presentation fill',(-2.5,-4,2.8),(0,0,1.2),32,(1,.79,.60),3)
for x in [-.30,.30]:
    area('Roof light on porcelain',(x,-.31,2.035),(x*.63,-.09,2.23),3.5,(1,.70,.37),.065)
# Matte ground beyond the diorama catches a restrained blue-hour shadow.
groundmat=material('Backdrop blue slate',(.025,.043,.063),.95)
box('Endless dusk ground',(0,0,-.145),(200,200,.08),groundmat)

# Three-quarter front-left view: side fortunes, both foxes and all controls remain exposed.
bpy.ops.object.camera_add(location=(-3.55,-5.85,2.87))
cam=bpy.context.object;cam.name='Presentation • roadside shrine'
target=Vector((.12,-.02,1.28))
cam.rotation_euler=(target-cam.location).to_track_quat('-Z','Y').to_euler()
cam.data.type='PERSP';cam.data.lens=49
bpy.context.scene.camera=cam
cam.data.dof.use_dof=False

scene=bpy.context.scene
scene.render.engine='CYCLES'
scene.cycles.samples=64
scene.cycles.use_denoising=True
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.view_settings.view_transform='AgX'
scene.view_settings.look='AgX - Medium High Contrast'
scene.view_settings.exposure=.25
# Subtle native glow, not a substitute for actual illumination.
scene.use_nodes=True
nt=scene.node_tree;nt.nodes.clear()
rl=nt.nodes.new('CompositorNodeRLayers')
gl=nt.nodes.new('CompositorNodeGlare');gl.glare_type='FOG_GLOW';gl.quality='HIGH'
gl.threshold=1.6
co=nt.nodes.new('CompositorNodeComposite')
nt.links.new(rl.outputs['Image'],gl.inputs['Image'])
nt.links.new(gl.outputs['Image'],co.inputs['Image'])
# Convert typography to real exportable geometry.
for ob in list(bpy.data.objects):
    if ob.type=='FONT':
        bpy.ops.object.select_all(action='DESELECT')
        ob.select_set(True);bpy.context.view_layer.objects.active=ob
        bpy.ops.object.convert(target='MESH')
bpy.ops.object.select_all(action='DESELECT')
os.makedirs('/mnt/data',exist_ok=True)
scene.render.filepath='/mnt/data/shrine.png'
bpy.ops.wm.save_as_mainfile(filepath='/mnt/data/scene.blend')
```