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

# ---------- reset ----------
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
for datablocks in (bpy.data.meshes, bpy.data.curves, bpy.data.materials, bpy.data.cameras, bpy.data.lights):
    pass
scene = bpy.context.scene

# ---------- collections ----------
def new_coll(name):
    c = bpy.data.collections.get(name) or bpy.data.collections.new(name)
    if c.name not in scene.collection.children:
        scene.collection.children.link(c)
    return c
craft_coll = new_coll('YGG-1 | Craft')
wing_coll = new_coll('YGG-1 | Three Rear Wings')
sys_coll = new_coll('YGG-1 | Survey Systems')
pres_coll = new_coll('YGG-1 | Presentation')
star_coll = new_coll('YGG-1 | Starfield')

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

def parent_root(obj):
    if obj is not root:
        obj.parent = root

# ---------- materials ----------
def principled_mat(name, color, metallic=0.0, rough=0.4, emission=None, emission_strength=0.0, transmission=0.0, ior=1.45):
    m = bpy.data.materials.new(name)
    m.use_nodes = True
    bs = m.node_tree.nodes.get('Principled BSDF')
    if bs:
        if bs.inputs.get('Base Color'): bs.inputs['Base Color'].default_value = (*color, 1.0)
        if bs.inputs.get('Metallic'): bs.inputs['Metallic'].default_value = metallic
        if bs.inputs.get('Roughness'): bs.inputs['Roughness'].default_value = rough
        if bs.inputs.get('IOR'): bs.inputs['IOR'].default_value = ior
        if bs.inputs.get('Transmission Weight'): bs.inputs['Transmission Weight'].default_value = transmission
        elif bs.inputs.get('Transmission'): bs.inputs['Transmission'].default_value = transmission
        if bs.inputs.get('Coat Weight'): bs.inputs['Coat Weight'].default_value = 0.22
        if bs.inputs.get('Coat Roughness'): bs.inputs['Coat Roughness'].default_value = 0.18
        if bs.inputs.get('Sheen Weight'): bs.inputs['Sheen Weight'].default_value = 0.08
        if bs.inputs.get('Iridescence Weight'): bs.inputs['Iridescence Weight'].default_value = 0.12
        if bs.inputs.get('Iridescence IOR'): bs.inputs['Iridescence IOR'].default_value = 1.45
        if emission is not None:
            if bs.inputs.get('Emission Color'): bs.inputs['Emission Color'].default_value = (*emission, 1.0)
            elif bs.inputs.get('Emission'): bs.inputs['Emission'].default_value = (*emission, 1.0)
            if bs.inputs.get('Emission Strength'): bs.inputs['Emission Strength'].default_value = emission_strength
    m.diffuse_color = (*color, 1.0)
    return m

pearl = principled_mat('Pearl-white ceramic composite', (0.72,0.78,0.83), metallic=0.24, rough=0.25)
pearl_hi = principled_mat('Ceramic edge highlights', (0.90,0.94,0.98), metallic=0.18, rough=0.20)
pearl_panel = principled_mat('Large service panel satin', (0.48,0.56,0.62), metallic=0.38, rough=0.30)
gunmetal = principled_mat('Dark gunmetal internal structure', (0.035,0.052,0.068), metallic=0.86, rough=0.28)
heat = principled_mat('Heat-resistant engine recess', (0.008,0.014,0.024), metallic=0.72, rough=0.22)
heat2 = principled_mat('Engine channel inner wall', (0.018,0.028,0.050), metallic=0.55, rough=0.30)
anodized = principled_mat('Subtle anodized violet accent', (0.16,0.055,0.22), metallic=0.68, rough=0.26)
window = principled_mat('Flush survey aperture glass', (0.008,0.035,0.065), metallic=0.32, rough=0.12, transmission=0.12)
canopy = principled_mat('Dark transparent canopy glazing', (0.008,0.025,0.045), metallic=0.12, rough=0.11, transmission=0.62, ior=1.46)
cyan = principled_mat('Cyan suspension field', (0.01,0.11,0.24), metallic=0.05, rough=0.15, emission=(0.02,0.55,1.0), emission_strength=9.0)
violet = principled_mat('Violet suspension field', (0.10,0.015,0.28), metallic=0.05, rough=0.18, emission=(0.22,0.015,0.95), emission_strength=8.0)
bluewhite = principled_mat('Contained white-blue engine energy', (0.26,0.62,0.96), metallic=0.12, rough=0.12, emission=(0.45,0.82,1.0), emission_strength=16.0)
bluecore = principled_mat('Deep engine core', (0.02,0.12,0.28), metallic=0.25, rough=0.14, emission=(0.04,0.28,0.72), emission_strength=7.0)
amber = principled_mat('Restrained warm amber console', (0.42,0.075,0.008), metallic=0.08, rough=0.20, emission=(1.0,0.16,0.015), emission_strength=7.0)
textmat = principled_mat('Serial marking', (0.018,0.032,0.045), metallic=0.55, rough=0.25)
star_white = principled_mat('Starfield white', (0.7,0.85,1.0), emission=(0.65,0.84,1.0), emission_strength=4.0)
star_blue = principled_mat('Starfield blue', (0.15,0.35,1.0), emission=(0.15,0.35,1.0), emission_strength=3.0)
star_dim = principled_mat('Starfield dim', (0.25,0.3,0.4), emission=(0.25,0.3,0.4), emission_strength=1.5)

# ---------- root ----------
bpy.ops.object.empty_add(type='PLAIN_AXES', location=(0,0,0))
root = bpy.context.object
root.name = 'YGG-1 | compact deep-space survey ship'
move_to(root, craft_coll)

# ---------- geometry helpers ----------
def add_bevel(obj, width=0.08, segments=3):
    if width <= 0: return
    mod = obj.modifiers.new('Soft engineered edge', 'BEVEL')
    mod.width = width
    mod.segments = segments
    mod.limit_method = 'ANGLE'
    try: mod.harden_normals = True
    except: pass

def add_cube(name, loc, dims, mat, bevel=0.06, coll=craft_coll, rot=(0,0,0)):
    bpy.ops.mesh.primitive_cube_add(location=loc, rotation=rot)
    o = bpy.context.object; o.name=name; o.dimensions=dims
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    if mat: o.data.materials.append(mat)
    add_bevel(o, bevel, 3)
    move_to(o, coll); parent_root(o)
    return o

def add_cyl(name, loc, radius, depth, mat, direction=(0,0,1), coll=craft_coll, vertices=32, bevel=0.02):
    d = Vector(direction).normalized()
    bpy.ops.mesh.primitive_cylinder_add(vertices=vertices, radius=radius, depth=depth, location=loc, rotation=d.to_track_quat('Z','Y').to_euler())
    o=bpy.context.object; o.name=name
    if mat: o.data.materials.append(mat)
    add_bevel(o, bevel, 2)
    move_to(o,coll); parent_root(o)
    return o

def add_uv(name, loc, scale, mat, coll=craft_coll, segments=32, rings=16):
    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)
    if mat: o.data.materials.append(mat)
    for p in o.data.polygons: p.use_smooth=True
    move_to(o,coll); parent_root(o)
    return o

def add_torus(name, loc, major, minor, mat, rot=(0,0,0), coll=sys_coll):
    bpy.ops.mesh.primitive_torus_add(major_radius=major, minor_radius=minor, major_segments=40, minor_segments=10, location=loc, rotation=rot)
    o=bpy.context.object; o.name=name
    if mat: o.data.materials.append(mat)
    for p in o.data.polygons: p.use_smooth=True
    move_to(o,coll); parent_root(o)
    return o

def add_between(name, a, b, radius, mat, coll=sys_coll, vertices=20, bevel=0.0):
    a,b=Vector(a),Vector(b); d=b-a
    o=add_cyl(name,(a+b)/2,radius,d.length,mat,direction=d,coll=coll,vertices=vertices,bevel=bevel)
    return o

def loft_mesh(name, stations, sides, mat, coll=craft_coll, smooth=True, bevel=0.0):
    # stations: (x, ry, rz, zcenter)
    verts=[]; faces=[]
    for x,ry,rz,zc in stations:
        for j in range(sides):
            th=2*pi*j/sides
            verts.append((x, ry*cos(th), zc+rz*sin(th)))
    for i in range(len(stations)-1):
        for j in range(sides):
            a=i*sides+j; b=i*sides+(j+1)%sides; c=(i+1)*sides+(j+1)%sides; d=(i+1)*sides+j
            faces.append((a,b,c,d))
    faces.append(tuple(reversed(tuple(range(sides)))))
    last=(len(stations)-1)*sides
    faces.append(tuple(last+j for j in range(sides)))
    me=bpy.data.meshes.new(name+' mesh'); me.from_pydata(verts,[],faces); me.update()
    o=bpy.data.objects.new(name,me); coll.objects.link(o)
    if mat: me.materials.append(mat)
    if smooth:
        for p in me.polygons: p.use_smooth=True
    if bevel: add_bevel(o,bevel,3)
    parent_root(o)
    return o

# ---------- central body ----------
body_stations=[
    (2.92,0.16,0.16,0.00),
    (2.62,0.52,0.40,0.00),
    (2.06,0.96,0.72,0.00),
    (0.72,1.40,1.08,0.00),
    (-0.90,1.57,1.22,0.00),
    (-2.05,1.54,1.19,0.00),
    (-2.68,1.28,1.00,0.00),
]
body=loft_mesh('Central compact wedge fuselage',body_stations,24,pearl,craft_coll,True,0.055)
# dark aft power collar, slightly proud of hull
collar_stations=[(-2.76,1.18,0.92,0),(-2.52,1.38,1.06,0),(-2.16,1.48,1.14,0),(-1.96,1.45,1.12,0)]
collar=loft_mesh('Aft gunmetal power collar',collar_stations,24,gunmetal,craft_coll,True,0.035)
# aft ceramic lip and a darker heat band
lip_stations=[(-2.83,1.15,0.88,0),(-2.75,1.24,0.95,0)]
loft_mesh('Aft ceramic service lip',lip_stations,24,pearl_hi,craft_coll,True,0.025)

# broad sparse hull panels
add_beveled_cube('Near-side service hatch',(-0.48,-1.53,0.55),(1.16,0.045,0.48),pearl_panel,0.075,craft_coll,rot=(0,0,0))
add_beveled_cube('Near-side hatch seam',(-0.48,-1.558,0.55),(0.035,0.018,0.40),gunmetal,0.012,craft_coll)
add_beveled_cube('Upper service hatch',(-0.72,0.0,1.26),(0.95,0.62,0.045),pearl_panel,0.10,craft_coll)
# subtle dark lower keel plate
add_beveled_cube('Lower thermal keel',(-0.55,0,-1.12),(2.15,0.88,0.10),heat,0.045,craft_coll)
# small aft ring seams as torus around longitudinal axis
add_torus('Aft collar seam',(-2.18,0,0),1.46,0.025,anodized,rot=(0,pi/2,0),coll=craft_coll)

# ---------- cockpit ----------
def canopy_mesh():
    rings=[(2.48,0.23,0.15,0.66),(2.22,0.47,0.27,0.75),(1.62,0.61,0.34,0.79),(1.18,0.48,0.25,0.76)]
    sides=8; verts=[]; faces=[]
    for x,ry,rz,zc in rings:
        for j in range(sides):
            th=2*pi*j/sides
            verts.append((x,ry*cos(th),zc+rz*sin(th)))
    for i in range(len(rings)-1):
        for j in range(sides):
            a=i*sides+j; b=i*sides+(j+1)%sides; c=(i+1)*sides+(j+1)%sides; d=(i+1)*sides+j
            faces.append((a,b,c,d))
    faces.append(tuple(reversed(range(sides))))
    faces.append(tuple((len(rings)-1)*sides+j for j in range(sides)))
    me=bpy.data.meshes.new('Faceted canopy mesh'); me.from_pydata(verts,[],faces); me.update()
    o=bpy.data.objects.new('Small faceted transparent cockpit canopy',me); craft_coll.objects.link(o); me.materials.append(canopy)
    for p in me.polygons: p.use_smooth=False
    parent_root(o); return o
can=canopy_mesh()
# cockpit interior: small seat silhouette and amber console
add_beveled_cube('Compact pilot seat back',(1.48,0.08,0.60),(0.28,0.34,0.56),heat,0.07,sys_coll,rot=(0,-0.10,0))
add_beveled_cube('Compact pilot seat cushion',(1.73,0.08,0.38),(0.40,0.36,0.12),gunmetal,0.055,sys_coll,rot=(0,-0.14,0))
add_uv('Pilot head silhouette',(1.46,0.08,0.93),(0.12,0.12,0.14),heat,sys_coll,24,12)
add_beveled_cube('Warm amber survey console',(2.00,-0.22,0.78),(0.32,0.40,0.045),amber,0.018,sys_coll,rot=(0,0.18,0))
add_beveled_cube('Console dark bezel',(2.00,-0.22,0.745),(0.40,0.48,0.025),heat,0.012,sys_coll,rot=(0,0.18,0))
# canopy structural trim, kept delicate
add_between('Canopy near rail',(1.18,-0.49,0.79),(2.46,-0.24,0.66),0.035,gunmetal,sys_coll,16,0.008)
add_between('Canopy far rail',(1.18,0.49,0.79),(2.46,0.24,0.66),0.035,gunmetal,sys_coll,16,0.008)
add_between('Canopy crown rail',(1.20,0.0,1.02),(2.45,0.0,0.81),0.028,gunmetal,sys_coll,16,0.006)

# ---------- ribbon wing geometry ----------
def offset_path(path, widths):
    pts=[Vector(p) for p in path]; out=[]
    for i,p in enumerate(pts):
        if i==0: t=pts[1]-pts[0]
        elif i==len(pts)-1: t=pts[-1]-pts[-2]
        else: t=pts[i+1]-pts[i-1]
        t.normalize(); n=Vector((-t.y,t.x))
        out.append((p+n*widths[i],p-n*widths[i]))
    return out

def map_plane(xr, layer, mode, sign=1, center=0.0):
    x,r=xr
    if mode=='lat': return (x, sign*r, center+layer)
    return (x, center+layer, r)

def wing_prism(name,path,widths,thick,mode,sign,center,mat,coll=wing_coll,bevel=0.14):
    lr=offset_path(path,widths); verts=[]
    for pL,pR in lr:
        verts.extend([map_plane(pL,-thick/2,mode,sign,center),map_plane(pR,-thick/2,mode,sign,center),map_plane(pL,thick/2,mode,sign,center),map_plane(pR,thick/2,mode,sign,center)])
    faces=[]; n=len(lr)
    for i in range(n-1):
        a=4*i; b=4*(i+1)
        faces += [(a,b,b+2,a+2),(a+1,a+3,b+3,b+1),(a+2,b+2,b+3,a+3),(a+1,b+1,b+3,a+3)]
    faces += [(0,1,3,2),(4*(n-1),4*(n-1)+2,4*(n-1)+3,4*(n-1)+1)]
    me=bpy.data.meshes.new(name+' mesh'); me.from_pydata(verts,[],faces); me.update()
    o=bpy.data.objects.new(name,me); coll.objects.link(o); me.materials.append(mat)
    for p in me.polygons: p.use_smooth=True
    add_bevel(o,bevel,4); parent_root(o)
    return o

def wing_surface(name,path,widths,mode,sign,center,layer,mat,coll=wing_coll):
    lr=offset_path(path,widths); verts=[]
    for pL,pR in lr:
        verts.extend([map_plane(pL,layer,mode,sign,center),map_plane(pR,layer,mode,sign,center)])
    faces=[]
    for i in range(len(lr)-1):
        faces.append((2*i,2*i+2,2*i+3,2*i+1))
    me=bpy.data.meshes.new(name+' mesh'); me.from_pydata(verts,[],faces); me.update()
    o=bpy.data.objects.new(name,me); coll.objects.link(o); me.materials.append(mat)
    parent_root(o); return o

wing_path=[(-1.52,2.28),(-1.52,2.72),(-1.53,3.10),(-1.68,3.47),(-2.10,3.90),(-2.82,4.36),(-3.82,4.82),(-4.98,5.27),(-6.18,5.66)]
wing_widths=[0.48,0.52,0.58,0.64,0.64,0.57,0.46,0.31,0.075]
for label,sign in [('Port',-1),('Starboard',1)]:
    wing_prism('Lateral '+label+' rear-swept hook wing',wing_path,wing_widths,0.78,'lat',sign,0.05,pearl,wing_coll,0.15)
    # dark thermal underside
    wing_surface(label+' wing heat underside',wing_path,[w*0.86 for w in wing_widths],'lat',sign,0.05,-0.405,heat,wing_coll)
    # elongated recessed propulsion channel on the upward skin
    eng_path=wing_path[1:]
    eng_w=[0.25,0.31,0.32,0.29,0.23,0.17,0.115,0.045]
    wing_surface(label+' deep propulsion channel',eng_path,eng_w,'lat',sign,0.05,0.445,heat2,wing_coll)
    core_path=wing_path[1:6]
    core_w=[0.105,0.13,0.14,0.10,0.055]
    wing_prism(label+' contained white-blue engine core',core_path,core_w,0.035,'lat',sign,0.50,bluewhite,wing_coll,0.025)
    # slim blue channel continuation toward the rear tip
    tail_path=wing_path[4:]
    wing_prism(label+' rear energy guide',tail_path,[0.050,0.048,0.035,0.023,0.012],0.028,'lat',sign,0.495,bluecore,wing_coll,0.018)
    # anodized base wash, floating with the module
    wing_surface(label+' anodized maglev base wash',wing_path[:3],[0.38,0.42,0.45],'lat',sign,0.05,0.447,anodized,wing_coll)
    # engine collar accents across the recess
    for k,(xx,rr) in enumerate([(-1.72,3.48),(-2.18,3.93)]):
        # short crossbar in the plane, visually inset
        add_beveled_cube(label+' engine channel rib '+str(k),(xx,sign*rr,0.49),(0.065,0.52,0.045),anodized,0.018,wing_coll)
    # engine point light deep in wing
    ld=bpy.data.lights.new(label+' engine white-blue point','POINT'); lo=bpy.data.objects.new(label+' engine white-blue point',ld); wing_coll.objects.link(lo); lo.location=(-2.05,sign*3.88,0.35); ld.energy=105; ld.color=(0.25,0.65,1.0); ld.shadow_soft_size=0.8; parent_root(lo)

# dorsal wing, same bent geometry in vertical plane
dorsal_path=wing_path
dorsal=wing_prism('Dorsal rear-swept hook wing',dorsal_path,wing_widths,0.82,'dorsal',1,0.0,pearl,wing_coll,0.15)
wing_surface('Dorsal wing heat far side',dorsal_path,[w*0.86 for w in wing_widths],'dorsal',1,0.0,0.415,heat,wing_coll)
# camera is on -Y, so near face is y negative
eng_path=wing_path[1:]
eng_w=[0.25,0.31,0.32,0.29,0.23,0.17,0.115,0.045]
wing_surface('Dorsal deep propulsion channel',eng_path,eng_w,'dorsal',1,0.0,-0.445,heat2,wing_coll)
core_path=wing_path[1:6]
core_w=[0.105,0.13,0.14,0.10,0.055]
wing_prism('Dorsal contained white-blue engine core',core_path,core_w,0.035,'dorsal',1,-0.50,bluewhite,wing_coll,0.025)
tail_path=wing_path[4:]
wing_prism('Dorsal rear energy guide',tail_path,[0.050,0.048,0.035,0.023,0.012],0.028,'dorsal',1,-0.495,bluecore,wing_coll,0.018)
wing_surface('Dorsal anodized maglev base wash',wing_path[:3],[0.38,0.42,0.45],'dorsal',1,0.0,-0.447,anodized,wing_coll)
for k,(xx,rr) in enumerate([(-1.72,3.48),(-2.18,3.93)]):
    add_beveled_cube('Dorsal engine channel rib '+str(k),(xx,-0.50,rr),(0.065,0.52,0.045),anodized,0.018,wing_coll)
ld=bpy.data.lights.new('Dorsal engine white-blue point','POINT'); lo=bpy.data.objects.new('Dorsal engine white-blue point',ld); wing_coll.objects.link(lo); lo.location=(-2.05,-0.28,3.88); ld.energy=125; ld.color=(0.25,0.65,1.0); ld.shadow_soft_size=0.8; parent_root(lo)

# ---------- maglev cradles, receiver plates and field filaments ----------
def add_maglev_lateral(label,sign):
    y=sign
    add_beveled_cube(label+' recessed hull emitter cradle',(-1.54,y*1.57,0.10),(0.78,0.18,0.64),gunmetal,0.09,sys_coll)
    # violet armored inset and cyan center on the outward face
    add_cyl(label+' violet emitter ring',(-1.54,y*1.69,0.10),0.25,0.065,violet,(0,y,0),sys_coll,32,0.008)
    add_cyl(label+' cyan emitter heart',(-1.54,y*1.735,0.10),0.125,0.035,cyan,(0,y,0),sys_coll,32,0.006)
    add_beveled_cube(label+' wing receiver armor',(-1.54,y*2.29,0.10),(0.82,0.16,0.68),gunmetal,0.09,sys_coll)
    add_torus(label+' violet receiver ring',(-1.54,y*2.18,0.10),0.25,0.035,violet,rot=(pi/2,0,0),coll=sys_coll)
    add_cyl(label+' cyan receiver heart',(-1.54,y*2.135,0.10),0.12,0.035,cyan,(0,y,0),sys_coll,32,0.006)
    # three curved magnetic field filaments, no physical strut
    for i,zoff in enumerate((-0.20,0.0,0.20)):
        xoff=(-0.16 + i*0.16)
        pts=[(-1.54+xoff,y*1.76,0.10+zoff),(-1.63+xoff*0.7,y*1.88,0.15+zoff*0.75),(-1.55+xoff*0.35,y*2.03,0.06+zoff*0.85),(-1.54+xoff,y*2.12,0.10+zoff)]
        curve_polyline(label+' magnetic field filament '+str(i),pts,0.018,violet,sys_coll)
    # soft field light placed in the empty gap
    ld=bpy.data.lights.new(label+' maglev violet glow','POINT'); lo=bpy.data.objects.new(label+' maglev violet glow',ld); sys_coll.objects.link(lo); lo.location=(-1.54,y*1.94,0.10); ld.energy=38; ld.color=(0.30,0.08,1.0); ld.shadow_soft_size=0.35; parent_root(lo)

def curve_polyline(name, points, bevel_depth, mat, coll=sys_coll):
    cu=bpy.data.curves.new(name+' curve','CURVE'); cu.dimensions='3D'; cu.resolution_u=2; cu.bevel_depth=bevel_depth; cu.bevel_resolution=3; cu.resolution_u=12
    sp=cu.splines.new('BEZIER'); sp.bezier_points.add(len(points)-1)
    for bp,co in zip(sp.bezier_points,points):
        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); parent_root(o); return o

add_maglev_lateral('Port',-1)
add_maglev_lateral('Starboard',1)
# dorsal cradle: plates on hull top and the floating dorsal base
add_beveled_cube('Dorsal recessed hull emitter cradle',(-1.54,0,1.42),(0.78,0.70,0.18),gunmetal,0.09,sys_coll)
add_cyl('Dorsal violet emitter ring',(-1.54,0,1.55),0.25,0.065,violet,(0,0,1),sys_coll,32,0.008)
add_cyl('Dorsal cyan emitter heart',(-1.54,0,1.595),0.12,0.035,cyan,(0,0,1),sys_coll,32,0.006)
add_beveled_cube('Dorsal wing receiver armor',(-1.54,0,2.29),(0.82,0.70,0.16),gunmetal,0.09,sys_coll)
add_torus('Dorsal violet receiver ring',(-1.54,0,2.18),0.25,0.035,violet,rot=(0,0,0),coll=sys_coll)
add_cyl('Dorsal cyan receiver heart',(-1.54,0,2.135),0.12,0.035,cyan,(0,0,-1),sys_coll,32,0.006)
for i,yoff in enumerate((-0.20,0.0,0.20)):
    pts=[(-1.54+yoff*0.75,0,1.62),(-1.63+yoff*0.45,-0.08+yoff*0.35,1.80),(-1.55+yoff*0.25,-0.04+yoff*0.20,2.01),(-1.54+yoff,0,2.12)]
    curve_polyline('Dorsal magnetic field filament '+str(i),pts,0.018, violet if i!=1 else cyan,sys_coll)
ld=bpy.data.lights.new('Dorsal maglev violet glow','POINT'); lo=bpy.data.objects.new('Dorsal maglev violet glow',ld); sys_coll.objects.link(lo); lo.location=(-1.54,-0.05,1.93); ld.energy=42; ld.color=(0.30,0.08,1.0); ld.shadow_soft_size=0.4; parent_root(lo)

# ---------- survey apertures and flush instrument windows ----------
# near-side dark optical strips
add_beveled_cube('Near flush survey aperture A',(0.40,-1.49,0.30),(0.70,0.055,0.15),window,0.045,sys_coll)
add_beveled_cube('Near flush survey aperture B',(-0.42,-1.50,0.20),(0.42,0.055,0.105),window,0.035,sys_coll)
add_beveled_cube('Near narrow instrument window',(-0.95,-1.38,0.78),(0.52,0.045,0.12),window,0.04,sys_coll,rot=(0.10,0,0))
# small cyan sensor panes nested in apertures
add_beveled_cube('Aperture A cyan slit',(0.40,-1.525,0.30),(0.40,0.012,0.025),cyan,0.01,sys_coll)
add_beveled_cube('Aperture B cyan slit',(-0.42,-1.535,0.20),(0.20,0.012,0.018),cyan,0.008,sys_coll)
# symmetric far side windows for orbit readability
add_beveled_cube('Far flush survey aperture',(0.25,1.50,0.28),(0.62,0.05,0.13),window,0.04,sys_coll)
# forward sensor, tiny and flush, not a weapon
add_cyl('Nose flush survey lens',(2.895,0,0.0),0.105,0.035,cyan,(1,0,0),sys_coll,32,0.006)
add_cyl('Nose lens dark bezel',(2.875,0,0.0),0.15,0.028,heat,(1,0,0),sys_coll,32,0.008)

# ---------- serial marking ----------
def add_text_mesh(name, body_text, loc, size, rot, mat, coll=craft_coll):
    cu=bpy.data.curves.new(name+' curve','FONT'); cu.body=body_text; cu.align_x='CENTER'; cu.align_y='CENTER'; cu.size=size; cu.extrude=0.008; cu.bevel_depth=0.002; cu.bevel_resolution=2
    o=bpy.data.objects.new(name,cu); coll.objects.link(o); o.location=loc; o.rotation_euler=rot; 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; parent_root(o); o.select_set(False); return o
add_text_mesh('Serial designation YGG-1','YGG-1',(0.12,-1.59,0.08),0.34,(pi/2,0,0),textmat,craft_coll)
add_text_mesh('Survey role marking','DEEP SPACE SURVEY',(-0.72,-1.575,-0.26),0.085,(pi/2,0,0),pearl_hi,craft_coll)

# ---------- starfield mesh ----------
random.seed(19)
verts=[]; faces=[]; mids=[]
# octahedral pinpricks on a spherical shell
for si in range(520):
    # random direction, shell radius variation
    z=random.uniform(-1,1); a=random.uniform(0,2*pi); rr=math.sqrt(max(0,1-z*z)); direction=Vector((rr*cos(a),rr*sin(a),z))
    radius=random.uniform(25,34); p=direction*radius
    s=random.choice([0.018,0.022,0.028,0.038,0.055])
    # local octahedron, varied tiny
    base=len(verts)
    verts.extend([(p.x+s,p.y,p.z),(p.x-s,p.y,p.z),(p.x,p.y+s,p.z),(p.x,p.y-s,p.z),(p.x,p.y,p.z+s),(p.x,p.y,p.z-s)])
    faces.extend([(base+0,base+2,base+4),(base+2,base+1,base+4),(base+1,base+3,base+4),(base+3,base+0,base+4),(base+2,base+0,base+5),(base+1,base+2,base+5),(base+3,base+1,base+5),(base+0,base+3,base+5)])
me=bpy.data.meshes.new('Deep space starfield mesh'); me.from_pydata(verts,[],faces); me.update()
stars=bpy.data.objects.new('Deep-space starfield',me); star_coll.objects.link(stars)
me.materials.append(star_white); me.materials.append(star_blue); me.materials.append(star_dim)
for poly in me.polygons:
    # group faces by octa index, slight color variety
    idx=(poly.index//8)%10
    poly.material_index=1 if idx in (1,6) else (2 if idx in (3,8,9) else 0)

# ---------- lighting ----------
def add_area(name, loc, energy, color, size, target=(0,0,0), coll=pres_coll):
    ld=bpy.data.lights.new(name,'AREA'); ld.energy=energy; ld.color=color; ld.shape='DISK'; ld.size=size
    o=bpy.data.objects.new(name,ld); coll.objects.link(o); o.location=loc; o.rotation_euler=(Vector(target)-o.location).to_track_quat('-Z','Y').to_euler(); parent_root(o); return o

def add_point(name, loc, energy, color, radius, coll=pres_coll):
    ld=bpy.data.lights.new(name,'POINT'); ld.energy=energy; ld.color=color; ld.shadow_soft_size=radius
    o=bpy.data.objects.new(name,ld); coll.objects.link(o); o.location=loc; parent_root(o); return o
add_area('Key pearl softbox',(8.5,-10.5,9.5),1350,(0.86,0.93,1.0),6.0,(-0.8,0,0.8))
add_area('Warm cockpit fill',(5.2,-6.0,2.5),360,(1.0,0.34,0.08),3.5,(1.5,0,0.5))
add_area('Cool far-side fill',(-1.0,8.0,5.0),1050,(0.12,0.32,1.0),5.0,(-2,0,1.2))
add_area('Violet rear rim',(-9.0,3.5,8.5),1700,(0.32,0.10,1.0),5.0,(-2,0,2.0))
add_area('Dorsal edge rim',(0.0,0.5,12.0),900,(0.25,0.62,1.0),4.0,(-2,0,2.5))
add_point('Hull underside blue bounce',(-1.0,-2.0,-2.8),260,(0.08,0.25,1.0),2.0)

# ---------- camera ----------
def point_camera(cam, target):
    cam.rotation_euler=(Vector(target)-cam.location).to_track_quat('-Z','Y').to_euler()
bpy.ops.object.camera_add(location=(12.8,-19.5,8.8))
cam=bpy.context.object; cam.name='YGG-1 presentation camera'; cam.data.lens=52; cam.data.sensor_width=36
point_camera(cam,(-1.55,0,1.15)); scene.camera=cam; move_to(cam,pres_coll)
# subtle DOF disabled for full ship clarity
cam.data.dof.use_dof=False

# ---------- world/render ----------
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.0015,0.0025,0.008,1); bg.inputs['Strength'].default_value=0.035
scene.render.engine='BLENDER_EEVEE_NEXT'
scene.render.resolution_x=768; scene.render.resolution_y=768; 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/ygg1_preview.png'
# transparent-friendly settings on canopy where available
try: canopy.surface_render_method='DITHERED'
except: pass
# color management
try:
    scene.view_settings.look='AgX - Medium High Contrast'
except: pass
scene.view_settings.exposure=0.6
# compositor bloom-like fog glow, works in Cycles and Eevee
scene.use_nodes=True
nt=scene.node_tree; nt.nodes.clear()
rl=nt.nodes.new('CompositorNodeRLayers'); glare=nt.nodes.new('CompositorNodeGlare'); glare.glare_type='FOG_GLOW'; glare.quality='HIGH'; glare.threshold=0.7; glare.size=7; glare.mix=0.0
comp=nt.nodes.new('CompositorNodeComposite'); nt.links.new(rl.outputs['Image'],glare.inputs['Image']); nt.links.new(glare.outputs['Image'],comp.inputs['Image'])
# metadata custom props for benchmark clarity
scene['ship_designation']='YGG-1'
scene['ship_role']='Compact deep-space survey and listening craft'
scene['wing_count']=3
scene['wing_geometry']='Three thick rear-swept bent hook wings with separated maglev bases'
scene['presentation_notes']='Frontal three-quarter view; dark starfield; amber cockpit, white-blue engine cores, cyan-violet field gaps'

# select root for a clean starting state
bpy.ops.object.select_all(action='DESELECT'); root.select_set(True); bpy.context.view_layer.objects.active=root
# save
bpy.ops.wm.save_as_mainfile(filepath='/tmp/YGG-1_survey_ship.blend')
# render preview using Eevee for fast validation
scene.render.engine='BLENDER_EEVEE_NEXT'
bpy.ops.render.render(write_still=True)
result={'objects':len(bpy.data.objects),'meshes':len(bpy.data.meshes),'materials':len(bpy.data.materials),'camera':cam.name,'filepath':scene.render.filepath}


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 174, in <module>
NameError: name 'add_beveled_cube' is not defined