"""Kingy.ai original starter scenes. Tested with Blender 5.2.1 LTS.
Run in a NEW Blender file: Scripting > Text > Open > Run Script.
Or: blender --background --python build_scene.py -- --project product --root /absolute/folder
No downloads, credentials, add-ons or third-party assets. MIT license; see LICENSE.txt.
Creates a new scene, never deletes existing scenes; refuses to overwrite a saved deliverable.
"""
import argparse
import json
import math
from pathlib import Path
import sys
import time
import bpy
from mathutils import Vector

# Change these two settings only when running from Blender's Text Editor.
PROJECT = 'product'  # product, animation, room, spacecraft
ROOT = Path.home() / 'Documents' / 'BlenderAstra'

def material(name, color, metallic=0, roughness=.4, emission=0):
    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['Metallic'].default_value = metallic
    p.inputs['Roughness'].default_value = roughness
    if emission:
        p.inputs['Emission Color'].default_value = (*color, 1)
        p.inputs['Emission Strength'].default_value = emission
    return m

def move_to(obj, collection):
    for c in list(obj.users_collection):
        c.objects.unlink(obj)
    collection.objects.link(obj)

def box(name, loc, dims, mat, collection, bevel=.015):
    bpy.ops.mesh.primitive_cube_add(size=1, location=loc)
    o = bpy.context.object
    o.name = name
    o.dimensions = dims
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    if bevel:
        mod = o.modifiers.new('Edge highlights', 'BEVEL')
        mod.width = bevel
        mod.segments = 3
    o.data.materials.append(mat)
    move_to(o, collection)
    return o

def cylinder(name, loc, radius, depth, mat, collection):
    bpy.ops.mesh.primitive_cylinder_add(vertices=48, radius=radius, depth=depth, location=loc)
    o = bpy.context.object
    o.name = name
    o.data.materials.append(mat)
    mod = o.modifiers.new('Soft edge', 'BEVEL'); mod.width=.006; mod.segments=3
    for polygon in o.data.polygons:
        polygon.use_smooth = len(polygon.vertices) == 4
    move_to(o, collection)
    return o

def aim(obj, target):
    obj.rotation_euler = (Vector(target) - obj.location).to_track_quat('-Z', 'Y').to_euler()

def camera(scene, collection, loc, target, lens=50):
    d = bpy.data.cameras.new('Camera'); d.lens = lens
    o = bpy.data.objects.new('Camera', d); collection.objects.link(o)
    o.location = loc; aim(o, target); scene.camera = o
    return o

def light(name, collection, loc, target, power, size, color=(1,1,1)):
    d = bpy.data.lights.new(name, 'AREA'); d.energy=power; d.shape='DISK'; d.size=size; d.color=color
    o = bpy.data.objects.new(name, d); collection.objects.link(o); o.location=loc; aim(o,target)
    return o

def animate_camera(cam, samples, target):
    # Explicit samples use smoothstep spacing, without version-sensitive F-curve access.
    start, end = Vector(samples[0]), Vector(samples[1])
    for f in range(1,193):
        t=(f-1)/191; s=t*t*(3-2*t)
        cam.location=start.lerp(end,s); aim(cam,target)
        cam.keyframe_insert(data_path='location',frame=f)
        cam.keyframe_insert(data_path='rotation_euler',frame=f)

def build(project, root, render=True):
    started=time.monotonic(); root=Path(root).expanduser().resolve()
    for folder in ['scenes','scripts','assets','previews','exports']:
        (root/folder).mkdir(parents=True,exist_ok=True)
    destination=root/'scenes'/f'{project}_v001.blend'
    if destination.exists():
        raise FileExistsError(f'Preserving existing file: {destination}. Choose a new root folder.')
    scene=bpy.data.scenes.new('Kingy_'+project)
    bpy.context.window.scene=scene
    collections={}
    for name in ['Product','Stage','Lights']:
        c=bpy.data.collections.new('Kingy_'+name); scene.collection.children.link(c); collections[name]=c
    product=collections['Product']; stage=collections['Stage']; lights=collections['Lights']
    scene.unit_settings.system='METRIC'; scene.unit_settings.scale_length=1
    scene.render.engine='CYCLES'; scene.cycles.samples=32; scene.cycles.use_denoising=True
    scene.cycles.device='CPU'
    scene.render.resolution_x=1920; scene.render.resolution_y=1080; scene.render.resolution_percentage=100
    scene.render.fps=24; scene.render.fps_base=1; scene.frame_start=1; scene.frame_end=192
    scene.render.image_settings.file_format='PNG'; scene.render.image_settings.color_mode='RGBA'
    scene.world=bpy.data.worlds.new('Kingy World'); scene.world.use_nodes=True
    scene.world.node_tree.nodes['Background'].inputs[0].default_value=(.11,.15,.22,1)
    scene.world.node_tree.nodes['Background'].inputs[1].default_value=.25
    metal=material('Satin titanium',(.31,.38,.43),.8,.28)
    dark=material('Graphite',(.012,.021,.028),.3,.32)
    cyan=material('Cyan indicator',(.01,.65,.85),.2,.22,2)
    ivory=material('Warm ceramic',(.75,.70,.59),.05,.32)
    wood=material('Honey timber',(.32,.14,.055),0,.45)
    ground=material('Midnight stage',(.016,.031,.045),.15,.38)
    if project in ['product','animation']:
        # Fictional enclosure: 140 mm wide, 100 mm deep, 45 mm high.
        box('Astra_TestObject', (0,0,.0675),(.14,.10,.045),metal,product,.006)
        box('Top panel',(0,0,.0905),(.126,.086,.002),dark,product,.004)
        box('Front inset',(0,-.0505,.067),(.113,.002,.022),dark,product,.003)
        box('Status strip',(.032,-.052,.068),(.032,.001,.002),cyan,product,.0004)
        for i in range(3):
            box('Port recess '+str(i),(-.035+i*.016,-.052,.066),(.01,.001,.007),dark,product,.001)
        for i in range(11):
            box('Top vent '+str(i),(-.047+i*.009,0,.092),(.003,.060,.001),metal,product,.0004)
        cylinder('Presentation plinth',(0,0,.021),.116,.042,ivory,stage)
        box('Studio floor',(0,0,-.009),(200,200,.01),ground,stage,0)
        scene.view_settings.exposure=-1.5
        cam=camera(scene,stage,(.39,-.494,.3085),(0,0,.055),55)
        light('Key softbox',lights,(-.20,-.15,.35),(0,0,.07),18,.25,(.72,.85,1))
        light('Warm rim',lights,(.18,.17,.22),(0,0,.06),24,.18,(1,.66,.38))
        light('Front fill',lights,(.02,-.30,.12),(0,0,.07),4,.20)
        if project=='animation':
            animate_camera(cam,[(.39,-.494,.3085),(-.364,-.494,.2305)],(0,0,.055))
    elif project=='room':
        plaster=material('Warm plaster',(.58,.53,.44),0,.8)
        fabric=material('Sage upholstery',(.12,.24,.20),0,.85)
        box('Floor',(0,0,-.10),(6,5,.20),wood,stage,.025)
        box('Back wall',(0,2.5,1.5),(6,.15,3),plaster,stage)
        box('Left wall',(-3,0,1.5),(.15,5,3),plaster,stage)
        # Open front and right side keep this a compact cutaway, not a complete house.
        box('Sofa base',(-.65,1.30,.28),(2.6,.85,.36),wood,product,.06)
        box('Sofa back',(-.65,1.66,.78),(2.6,.20,.78),fabric,product,.10)
        for i in range(3):
            box('Seat cushion '+str(i),(-1.49+i*.84,1.2,.56),(.80,.76,.20),fabric,product,.075)
        box('Woven rug',(.0,.05,.012),(3.6,2.0,.025),ivory,stage,.02)
        cylinder('Coffee table',(.1,.18,.48),.62,.09,wood,product)
        cylinder('Table base',(.1,.18,.25),.23,.44,dark,product)
        box('Book',(.10,.18,.55),(.30,.22,.045),ivory,product,.01)
        box('Low shelf',(-2.75,.4,.75),(.3,1.4,1.5),wood,product,.02)
        for z in [.35,.85,1.35]:
            box('Shelf ledge',(-2.5,.4,z),(.4,1.4,.05),ivory,product,.01)
        cylinder('Plant pot',(1.95,1.55,.25),.25,.5,ivory,product)
        for i in range(9):
            angle=i*2.4
            bpy.ops.mesh.primitive_uv_sphere_add(segments=12, ring_count=8, radius=1, location=(1.95+.20*math.cos(angle),1.55+.20*math.sin(angle),.75+i*.075))
            o=bpy.context.object; o.name='Stylized leaf'; o.scale=(.18,.08,.35); o.rotation_euler=(.4,angle,angle); o.data.materials.append(fabric); move_to(o,product)
        cylinder('Lamp base',(-2.2,1.4,.04),.2,.08,dark,product)
        cylinder('Lamp stem',(-2.2,1.4,.9),.02,1.8,metal,product)
        cylinder('Lamp shade',(-2.2,1.4,1.8),.28,.35,ivory,product)
        cam=camera(scene,stage,(2.7,-3.8,1.65),(-.4,1,.9),26)
        animate_camera(cam,[(2.7,-3.8,1.65),(1.4,-1.5,1.65)],(-.4,1,.9))
        light('Window softbox',lights,(3,-1,4),(0,1,0),700,4,(1,.79,.55))
        light('Ceiling bounce',lights,(-1,1,3),(0,0,0),350,3)
    elif project=='spacecraft':
        box('Hull',(0,0,0),(1.0,2.8,.40),ivory,product,.14)
        box('Cockpit',(0,-.55,.28),(.62,.85,.24),dark,product,.10)
        for side in [-1,1]:
            for y in [-.65,.7]:
                wing=box('Wing', (side*1.10,y,-.08),(1.5,.45,.12),metal,product,.03)
                wing.rotation_euler.z=side*.20
                box('Wingtip light',(side*1.79,y,-.04),(.10,.34,.13),cyan,product,.02)
            engine=cylinder('Engine',(side*.38,1.40,-.02),.20,.5,dark,product)
            engine.rotation_euler.x=math.pi/2
            nozzle=cylinder('Engine glow',(side*.38,1.66,-.02),.15,.025,cyan,product)
            nozzle.rotation_euler.x=math.pi/2
        box('Studio floor',(0,0,-.55),(200,200,.02),ground,stage,0)
        cam=camera(scene,stage,(5,-6,4),(0,0,0),48)
        light('Key',lights,(-3,-4,6),(0,0,0),1100,5)
        light('Rim',lights,(3,4,4),(0,0,0),1500,4,(.5,.8,1))
        bpy.ops.object.select_all(action='DESELECT')
        for obj in product.objects: obj.select_set(True)
        bpy.context.view_layer.objects.active=next(iter(product.objects))
        bpy.ops.export_scene.gltf(filepath=str(root/'exports'/'spacecraft.glb'),export_format='GLB',use_selection=True,export_apply=True,export_animations=False)
    else:
        raise ValueError(project)
    scene.frame_set(1)
    sequence=root/'exports'/f'{project}_frames'; sequence.mkdir(exist_ok=True)
    scene.render.filepath=f'//../exports/{project}_frames/frame_'
    bpy.ops.wm.save_as_mainfile(filepath=str(destination))
    if render:
        scene.render.resolution_percentage=50
        frames=[1,96,192] if project in ['animation','room'] else [1]
        for frame in frames:
            scene.frame_set(frame)
            scene.render.filepath=str(root/'previews'/f'{project}_{frame:04d}.png')
            bpy.ops.render.render(write_still=True)
    report={'blender':bpy.app.version_string,'project':project,'scene':scene.name,'meshes':len([o for o in scene.objects if o.type=='MESH']),'saved':str(destination),'seconds':round(time.monotonic()-started,2),'preview_frames':frames if render else []}
    (root/'previews'/f'{project}_build.json').write_text(json.dumps(report,indent=2))
    print('KINGY_RESULT '+json.dumps(report))

if __name__=='__main__':
    if '--' in sys.argv:
        p=argparse.ArgumentParser(); p.add_argument('--project',choices=['product','animation','room','spacecraft'],default=PROJECT); p.add_argument('--root',default=str(ROOT)); p.add_argument('--no-render',action='store_true')
        args=p.parse_args(sys.argv[sys.argv.index('--')+1:]); build(args.project,args.root,not args.no_render)
    else:
        build(PROJECT,ROOT)
