import bpy, math, mathutils
from mathutils import Vector
from mathutils.geometry import tessellate_polygon
from math import sin, cos, pi

# ---------- clean scene ----------
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

# ---------- collections ----------
def make_collection(name):
    c = bpy.data.collections.get(name) or bpy.data.collections.new(name)
    if c.name not in bpy.context.scene.collection.children:
        try: bpy.context.scene.collection.children.link(c)
        except: pass
    return c
fish_col = make_collection('GOLDFISH_HERO')
env_col = make_collection('WATERY_STAGE')
detail_col = make_collection('FISH_DETAILS')
light_col = make_collection('STUDIO_LIGHTING')

def move_to_collection(obj, col):
    # objects made by operators are linked to the current collection; move them cleanly
    for c in list(obj.users_collection):
        c.objects.unlink(obj)
    col.objects.link(obj)

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

# ---------- materials ----------
def principled_mat(name, color, roughness=0.4, metallic=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')
    out.location=(300,0); bs.location=(0,0)
    set_input(bs,'Base Color',(*color,1))
    set_input(bs,'Roughness',roughness)
    set_input(bs,'Metallic',metallic)
    if emission is not None:
        set_input(bs,'Emission Color',(*emission,1))
        set_input(bs,'Emission Strength',emission_strength)
    nt.links.new(bs.outputs['BSDF'],out.inputs['Surface'])
    m.diffuse_color=(*color,1)
    return m

# Hero body with layered procedural color and bump
body_mat=bpy.data.materials.new('Goldfish Orange • subtle scales')
body_mat.use_nodes=True
nt=body_mat.node_tree; nt.nodes.clear()
out=nt.nodes.new('ShaderNodeOutputMaterial'); out.location=(600,0)
bs=nt.nodes.new('ShaderNodeBsdfPrincipled'); bs.location=(330,0)
set_input(bs,'Roughness',0.3); set_input(bs,'Metallic',0.04)
set_input(bs,'IOR',1.45); set_input(bs,'Coat Weight',0.18); set_input(bs,'Coat Roughness',0.16)
set_input(bs,'Subsurface Weight',0.035); set_input(bs,'Subsurface Radius',(1.0,0.35,0.15))
tex=nt.nodes.new('ShaderNodeTexCoord'); tex.location=(-650,80)
noise=nt.nodes.new('ShaderNodeTexNoise'); noise.location=(-470,100)
noise.inputs['Scale'].default_value=6.0; noise.inputs['Detail'].default_value=4.5; noise.inputs['Roughness'].default_value=.62
ramp=nt.nodes.new('ShaderNodeValToRGB'); ramp.location=(-180,120)
ramp.color_ramp.elements[0].position=.18; ramp.color_ramp.elements[0].color=(0.34,0.018,0.002,1)
ramp.color_ramp.elements[1].position=.82; ramp.color_ramp.elements[1].color=(1.0,0.29,0.015,1)
e=ramp.color_ramp.elements.new(.53); e.color=(0.86,0.105,0.006,1)
bump=nt.nodes.new('ShaderNodeBump'); bump.location=(100,-130)
bump.inputs['Strength'].default_value=.13; bump.inputs['Distance'].default_value=.055
nt.links.new(tex.outputs['Generated'],noise.inputs['Vector'])
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'])
body_mat.diffuse_color=(0.95,0.14,0.008,1)

scale_mat=principled_mat('Raised golden scale glints',(1.0,0.36,0.025),.25,.08)
set_input(next(n for n in scale_mat.node_tree.nodes if n.type=='BSDF_PRINCIPLED'),'Coat Weight',.3)
set_input(next(n for n in scale_mat.node_tree.nodes if n.type=='BSDF_PRINCIPLED'),'Coat Roughness',.12)

fin_mat=bpy.data.materials.new('Translucent orange fins • luminous edges')
fin_mat.use_nodes=True
nt=fin_mat.node_tree; nt.nodes.clear()
out=nt.nodes.new('ShaderNodeOutputMaterial'); out.location=(600,0)
bs=nt.nodes.new('ShaderNodeBsdfPrincipled'); bs.location=(340,0)
set_input(bs,'Roughness',.22); set_input(bs,'IOR',1.33); set_input(bs,'Transmission Weight',.24)
set_input(bs,'Subsurface Weight',.10); set_input(bs,'Subsurface Radius',(1.0,.25,.12))
set_input(bs,'Coat Weight',.22); set_input(bs,'Coat Roughness',.15); set_input(bs,'Alpha',.9)
layer=nt.nodes.new('ShaderNodeLayerWeight'); layer.location=(-460,80)
r=nt.nodes.new('ShaderNodeValToRGB'); r.location=(-190,80)
r.color_ramp.elements[0].position=.0; r.color_ramp.elements[0].color=(1.0,.46,.16,1)
r.color_ramp.elements[1].position=.78; r.color_ramp.elements[1].color=(.82,.075,.008,1)
mid=r.color_ramp.elements.new(.42); mid.color=(1.0,.20,.025,1)
nt.links.new(layer.outputs['Facing'],r.inputs['Fac']); nt.links.new(r.outputs['Color'],bs.inputs['Base Color'])
nt.links.new(bs.outputs['BSDF'],out.inputs['Surface'])
fin_mat.diffuse_color=(1.0,.18,.025,.78)
for prop,val in [('surface_render_method','DITHERED'),('blend_method','HASHED')]:
    try: setattr(fin_mat,prop,val); break
    except: pass

vein_mat=principled_mat('Fin veins',(0.52,.025,.003),.27,.04)
set_input(next(n for n in vein_mat.node_tree.nodes if n.type=='BSDF_PRINCIPLED'),'Coat Weight',.25)
edge_mat=principled_mat('Fin edge highlight',(1.0,.42,.07),.2,.02, emission=(1.0,.08,.01), emission_strength=.12)

black_mat=principled_mat('Deep glossy pupil',(0.002,.001,.0005),.08,.05)
set_input(next(n for n in black_mat.node_tree.nodes if n.type=='BSDF_PRINCIPLED'),'Coat Weight',.65)
white_mat=principled_mat('Eye catchlight',(1.0,.8,.48),.08,.05, emission=(1.0,.5,.12), emission_strength=.25)
iris_mat=principled_mat('Amber eye rings',(1.0,.36,.025),.23,.12)
gill_mat=principled_mat('Gill crease',(0.38,.012,.002),.35,.0)

floor_mat=bpy.data.materials.new('Aquatic turquoise star floor')
floor_mat.use_nodes=True
nt=floor_mat.node_tree; nt.nodes.clear()
out=nt.nodes.new('ShaderNodeOutputMaterial'); out.location=(450,0)
bs=nt.nodes.new('ShaderNodeBsdfPrincipled'); bs.location=(180,0)
set_input(bs,'Roughness',.33); set_input(bs,'Metallic',.18); set_input(bs,'Coat Weight',.2)
tex=nt.nodes.new('ShaderNodeTexNoise'); tex.location=(-320,20); tex.inputs['Scale'].default_value=2.4; tex.inputs['Detail'].default_value=2.0
r=nt.nodes.new('ShaderNodeValToRGB'); r.location=(-70,40)
r.color_ramp.elements[0].color=(.008,.12,.16,1); r.color_ramp.elements[1].color=(.04,.48,.50,1)
b=nt.nodes.new('ShaderNodeBump'); b.location=(0,-130); b.inputs['Strength'].default_value=.16; b.inputs['Distance'].default_value=.12
nt.links.new(tex.outputs['Fac'],r.inputs['Fac']); nt.links.new(r.outputs['Color'],bs.inputs['Base Color']); nt.links.new(tex.outputs['Fac'],b.inputs['Height']); nt.links.new(b.outputs['Normal'],bs.inputs['Normal']); nt.links.new(bs.outputs['BSDF'],out.inputs['Surface'])
floor_mat.diffuse_color=(.02,.32,.36,1)
star_side_mat=principled_mat('Star floor sides',(0.006,.065,.09),.4,.12)

back_mat=bpy.data.materials.new('Calm watery blue backdrop')
back_mat.use_nodes=True
nt=back_mat.node_tree; nt.nodes.clear()
out=nt.nodes.new('ShaderNodeOutputMaterial'); out.location=(560,0)
bs=nt.nodes.new('ShaderNodeBsdfPrincipled'); bs.location=(280,0)
set_input(bs,'Roughness',.42); set_input(bs,'Metallic',.04); set_input(bs,'Coat Weight',.12)
tex=nt.nodes.new('ShaderNodeTexCoord'); tex.location=(-620,100)
noise=nt.nodes.new('ShaderNodeTexNoise'); noise.location=(-430,100); noise.inputs['Scale'].default_value=1.15; noise.inputs['Detail'].default_value=3.0; noise.inputs['Roughness'].default_value=.55
r=nt.nodes.new('ShaderNodeValToRGB'); r.location=(-150,100)
r.color_ramp.elements[0].color=(.003,.025,.07,1); r.color_ramp.elements[0].position=.18
r.color_ramp.elements[1].color=(.018,.24,.31,1); r.color_ramp.elements[1].position=.84
mid=r.color_ramp.elements.new(.5); mid.color=(.005,.09,.16,1)
b=nt.nodes.new('ShaderNodeBump'); b.location=(30,-120); b.inputs['Strength'].default_value=.08; b.inputs['Distance'].default_value=.18
nt.links.new(tex.outputs['Generated'],noise.inputs['Vector']); nt.links.new(noise.outputs['Fac'],r.inputs['Fac']); nt.links.new(r.outputs['Color'],bs.inputs['Base Color']); nt.links.new(noise.outputs['Fac'],b.inputs['Height']); nt.links.new(b.outputs['Normal'],bs.inputs['Normal']); nt.links.new(bs.outputs['BSDF'],out.inputs['Surface'])
back_mat.diffuse_color=(.005,.09,.16,1)

plant_mat=principled_mat('Backdrop seaweed',(0.008,.12,.13),.45,.02)
ripple_mat=principled_mat('Water ripple accents',(.05,.55,.58),.22,.1, emission=(.02,.35,.42), emission_strength=.2)

bubble_mat=bpy.data.materials.new('Soft glass bubbles')
bubble_mat.use_nodes=True
nt=bubble_mat.node_tree; nt.nodes.clear(); out=nt.nodes.new('ShaderNodeOutputMaterial'); out.location=(300,0); bs=nt.nodes.new('ShaderNodeBsdfPrincipled'); bs.location=(0,0)
set_input(bs,'Base Color',(.12,.65,.75,1)); set_input(bs,'Roughness',.06); set_input(bs,'IOR',1.33); set_input(bs,'Transmission Weight',.8); set_input(bs,'Alpha',.24); set_input(bs,'Coat Weight',.5); set_input(bs,'Coat Roughness',.04)
nt.links.new(bs.outputs['BSDF'],out.inputs['Surface']); bubble_mat.diffuse_color=(.1,.65,.75,.24)
for prop,val in [('surface_render_method','DITHERED'),('blend_method','HASHED')]:
    try: setattr(bubble_mat,prop,val); break
    except: pass

# ---------- geometry helpers ----------
def mesh_obj(name, verts, faces, mat, col, smooth=False):
    me=bpy.data.meshes.new(name+'__Mesh'); me.from_pydata(verts,[],faces); me.update()
    ob=bpy.data.objects.new(name,me); col.objects.link(ob)
    if mat: me.materials.append(mat)
    if smooth:
        for p in me.polygons: p.use_smooth=True
    return ob

def add_bevel(ob,width=.03,segments=2):
    mod=ob.modifiers.new('Soft rounded edges','BEVEL'); mod.width=width; mod.segments=segments
    return mod

def create_curve(name, points, bevel, mat, col, cyclic=False):
    cu=bpy.data.curves.new(name+'__Curve','CURVE'); cu.dimensions='3D'; cu.resolution_u=16; cu.bevel_depth=bevel; cu.bevel_resolution=3; cu.resolution_u=24
    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'
    sp.use_cyclic_u=cyclic
    ob=bpy.data.objects.new(name,cu); col.objects.link(ob); cu.materials.append(mat)
    return ob

def create_fin(name, coords, thickness, mat, col):
    pts=[Vector(c) for c in coords]
    # average normal from first non-degenerate triangle
    normal=None
    for i in range(1,len(pts)-1):
        n=(pts[i]-pts[0]).cross(pts[i+1]-pts[0])
        if n.length>1e-6:
            normal=n.normalized(); break
    if normal is None: normal=Vector((0,1,0))
    front=[p+normal*thickness/2 for p in pts]; back=[p-normal*thickness/2 for p in pts]
    verts=[tuple(v) for v in front+back]
    # robust polygon tessellation
    tri_vecs=tessellate_polygon([pts])
    tris=[]
    for tri in tri_vecs:
        inds=[]
        for v in tri:
            inds.append(min(range(len(pts)),key=lambda k:(pts[k]-v).length))
        tris.append(tuple(inds))
    faces=[]
    for a,b,c in tris: faces.append((a,b,c))
    for a,b,c in tris: faces.append((len(pts)+c,len(pts)+b,len(pts)+a))
    n=len(pts)
    for i in range(n):
        j=(i+1)%n; faces.append((i,j,n+j,n+i))
    ob=mesh_obj(name,verts,faces,mat,col,False)
    # subtle bevel catches light on the membrane edge
    mod=ob.modifiers.new('Fin rim softness','BEVEL'); mod.width=min(thickness*.55,.025); mod.segments=2
    return ob

def add_uv(name, loc, scale, mat, col, segments=32, rings=20):
    bpy.ops.mesh.primitive_uv_sphere_add(segments=segments, ring_count=rings, location=loc)
    ob=bpy.context.object; ob.name=name; ob.scale=scale; bpy.ops.object.transform_apply(location=False,rotation=False,scale=True)
    move_to_collection(ob,col); ob.data.materials.append(mat)
    for p in ob.data.polygons: p.use_smooth=True
    return ob

def add_torus(name, loc, major, minor, rot, mat, col):
    bpy.ops.mesh.primitive_torus_add(major_radius=major, minor_radius=minor, major_segments=36, minor_segments=10, location=loc, rotation=rot)
    ob=bpy.context.object; ob.name=name; move_to_collection(ob,col); ob.data.materials.append(mat)
    for p in ob.data.polygons: p.use_smooth=True
    return ob

# ---------- fish body, a softly bending loft ----------
rings=[
    (-2.18,.22,.25,2.60,.02),
    (-2.05,.52,.47,2.66,.04),
    (-1.70,.79,.69,2.74,.06),
    (-1.12,1.00,.88,2.84,.09),
    (-.38,1.10,.98,2.88,.10),
    (.35,1.03,.90,2.74,.08),
    (1.02,.77,.68,2.58,.04),
    (1.50,.47,.43,2.60,.015),
    (1.72,.24,.23,2.63,.0),
]
seg=40; verts=[]; faces=[]
for x,ry,rz,zc,yc in rings:
    for j in range(seg):
        a=2*pi*j/seg
        verts.append((x,yc+ry*cos(a),zc+rz*sin(a)))
for i in range(len(rings)-1):
    for j in range(seg):
        a=i*seg+j; b=i*seg+(j+1)%seg; c=(i+1)*seg+(j+1)%seg; d=(i+1)*seg+j
        faces.append((a,b,c,d))
# end caps
verts.append((rings[0][0],rings[0][4],rings[0][3])); c0=len(verts)-1
verts.append((rings[-1][0],rings[-1][4],rings[-1][3])); c1=len(verts)-1
for j in range(seg):
    faces.append((c0,(j+1)%seg,j))
    a=(len(rings)-1)*seg+j; b=(len(rings)-1)*seg+(j+1)%seg
    faces.append((c1,a,b))
body=mesh_obj('Goldfish body • gentle S curve',verts,faces,body_mat,fish_col,True)

# ---------- tail and fins ----------
tail_upper=[(1.34,.01,2.72),(1.52,.04,2.98),(1.95,.10,3.38),(2.48,.16,3.86),(3.34,.23,4.28),(3.08,.17,3.46),(2.63,.07,2.72),(2.14,.00,2.62)]
tail_lower=[(1.34,.01,2.58),(1.52,.04,2.39),(1.96,.10,1.88),(2.50,.16,1.38),(3.30,.22,.98),(3.02,.16,1.76),(2.62,.07,2.38),(2.14,.00,2.58)]
upper=create_fin('Great flowing tail • upper lobe',tail_upper,.065,fin_mat,fish_col)
lower=create_fin('Great flowing tail • lower lobe',tail_lower,.065,fin_mat,fish_col)
# central tail membrane to visually join the two lobes
center_fin=[(1.30,-.01,2.72),(1.56,-.02,2.92),(2.20,-.02,2.76),(2.62,-.01,2.72),(2.18,-.02,2.56),(1.52,-.02,2.43),(1.30,-.01,2.58)]
create_fin('Tail root membrane',center_fin,.075,fin_mat,fish_col)

# dorsal fin follows the back in a small S wave
dorsal=[(-.83,.02,3.67),(-.55,.03,4.02),(-.18,.05,4.55),(.16,.08,4.78),(.47,.10,4.18),(.88,.08,3.30),(.68,.04,3.34),(-.36,.02,3.72)]
create_fin('Dorsal sail fin',dorsal,.055,fin_mat,fish_col)
# paired side fins, clearly three-dimensional and flowing backwards
ear_near=[(-1.02,-.64,2.86),(-.48,-.80,2.60),(-.14,-1.28,2.12),(-.02,-1.80,1.55),(-.46,-1.58,1.66),(-.90,-1.10,2.22)]
ear_far=[(-1.02,.64,2.86),(-.48,.80,2.60),(-.14,1.28,2.12),(-.02,1.80,1.55),(-.46,1.58,1.66),(-.90,1.10,2.22)]
create_fin('Near flowing side fin',ear_near,.052,fin_mat,fish_col)
create_fin('Far flowing side fin',ear_far,.052,fin_mat,fish_col)
# small pelvic fin for a believable silhouette
pelvic=[(.10,-.32,1.98),(.48,-.42,1.80),(.82,-.68,1.36),(.47,-.58,1.42),(.05,-.42,1.78)]
create_fin('Near pelvic fin',pelvic,.045,fin_mat,fish_col)

# tail ribs / fin rays, curved and warm
vein_paths=[
 [(1.48,-.07,2.74),(2.00,-.12,3.05),(2.62,-.12,3.68),(3.20,-.08,4.15)],
 [(1.50,-.08,2.74),(2.15,-.13,3.05),(2.74,-.12,3.40),(3.10,-.08,3.62)],
 [(1.50,-.08,2.68),(2.10,-.14,2.75),(2.70,-.12,2.78),(3.18,-.08,2.90)],
 [(1.48,-.07,2.62),(2.05,-.12,2.38),(2.67,-.12,1.74),(3.18,-.08,1.18)],
 [(1.48,-.07,2.62),(2.08,-.12,2.42),(2.68,-.12,2.10),(3.05,-.08,1.68)],
]
for i,p in enumerate(vein_paths): create_curve('Tail fin ray %02d'%i,p,.018,vein_mat,detail_col)
# a few delicate rays on dorsal and side fins
create_curve('Dorsal fin ray', [(-.60,-.03,3.76),(-.16,-.05,4.46),(.17,-.04,4.70),(.44,-.02,4.16)], .014, vein_mat, detail_col)
create_curve('Near pectoral ray',[(-.85,-.85,2.62),(-.38,-1.25,2.16),(-.08,-1.62,1.68)],.015,vein_mat,detail_col)

# ---------- eyes, rings, mouth and gill marks ----------
for side,label in [(-1,'Near'),(1,'Far')]:
    # body surface at the eye's height
    eyex=-1.72; eyez=2.99; ey=side*.73
    eye=add_uv(label+' eye • black globe',(eyex,ey,eyez),(.205,.18,.205),black_mat,detail_col)
    # warm iris ring is a torus whose plane is the side of the head
    add_torus(label+' eye • amber rim',(eyex,side*.887,eyez),.155,.035,(pi/2,0,0),iris_mat,detail_col)
    pupil=add_uv(label+' eye • pupil',(eyex,side*.925,eyez),(.095,.027,.112),black_mat,detail_col)
    # bright catchlight placed toward the camera-facing upper-front quadrant
    add_uv(label+' eye • catchlight',(eyex-.055,side*.955,eyez+.062),(.038,.018,.038),white_mat,detail_col,20,12)
    # gill arcs
    create_curve(label+' gill crease',[(-1.18,side*.79,2.92),(-.97,side*.84,2.67),(-1.13,side*.80,2.36)],.026,gill_mat,detail_col)
    create_curve(label+' gill echo',[(-1.08,side*.80,2.88),(-.91,side*.85,2.66),(-1.06,side*.81,2.46)],.012,edge_mat,detail_col)

# soft little mouth at the leading face
create_curve('Goldfish smiling mouth',[(-2.15,-.18,2.57),(-2.29,-.04,2.48),(-2.15,.18,2.57)],.035,gill_mat,detail_col)
# two tiny lips
add_uv('Upper lip',(-2.19,-.02,2.56),(.08,.06,.045),body_mat,detail_col,20,12)

# ---------- raised, subtle scale patches on both sides ----------
# patches are small convex leaf-like disks tangent to the lofted body
sv=[]; sf=[]
row_q=[-.58,-.31,-.04,.23,.49,.68]
for side in (-1,1):
    for ri,q in enumerate(row_q):
        # body x range and alternating stagger
        xs=[-1.47,-1.10,-.73,-.36,.01,.38,.75,1.05]
        offset=.18 if ri%2 else 0.0
        for x0 in xs:
            x=x0+offset
            # interpolate nearest ring values
            nearest=min(rings,key=lambda rr:abs(rr[0]-x))
            # smooth enough local dimensions from nearest ring
            _,ry,rz,zc,yc=nearest
            qq=max(-.78,min(.78,q + .03*sin(x*2.1)))
            phi=math.asin(qq)
            y=yc+side*ry*cos(phi)
            z=zc+rz*sin(phi)
            # tangent along cross section and outward normal
            t2=Vector((0,-side*ry*sin(phi),rz*cos(phi))).normalized()
            n=Vector((0,side*cos(phi),sin(phi))).normalized()
            t1=Vector((1,0,0))
            center=Vector((x,y,z))+n*.025
            w=.105; h=.095
            # a gently pointed scale silhouette; make center proud
            shape=[(-w,0),(-.72*w,.68*h),(0,h),(.72*w,.68*h),(w,0),(.64*w,-.55*h),(0,-.78*h),(-.64*w,-.55*h)]
            base=len(sv)
            for du,dv in shape:
                p=center+t1*du+t2*dv
                sv.append(tuple(p))
            peak=len(sv); sv.append(tuple(center+n*.035))
            # triangles ordered differently on the two sides for outward normals
            for k in range(8):
                j=(k+1)%8
                if side<0: sf.append((peak,base+k,base+j))
                else: sf.append((peak,base+j,base+k))
scales=mesh_obj('Subtle raised scale pattern',sv,sf,scale_mat,detail_col,True)

# ---------- stage: a very obvious five-point star floor ----------
def extruded_star(name, outer, inner, z0, z1):
    pts=[]
    for i in range(10):
        a=-pi/2 + i*pi/5
        r=outer if i%2==0 else inner
        pts.append((r*cos(a),r*sin(a)))
    verts=[(x,y,z0) for x,y in pts]+[(x,y,z1) for x,y in pts]
    faces=[]
    # top / bottom ngon, sides
    faces.append(tuple(range(10,20)))
    faces.append(tuple(reversed(range(0,10))))
    for i in range(10):
        j=(i+1)%10; faces.append((i,j,10+j,10+i))
    ob=mesh_obj(name,verts,faces,floor_mat,env_col,False)
    ob.data.materials.append(star_side_mat)
    for p in ob.data.polygons:
        if p.index>=2: p.material_index=1
    add_bevel(ob,.18,5)
    return ob
star=extruded_star('Child named star floor',6.5,3.05,0.0,.28)
# slim star inset/outline, sitting just above the star top
pts=[]
for i in range(10):
    a=-pi/2+i*pi/5; r=5.65 if i%2==0 else 2.65; pts.append((r*cos(a),r*sin(a),.292))
# outline as one closed curve, slightly luminous aqua
create_curve('Star floor inset outline',pts,.026,ripple_mat,detail_col,True)
# a few concentric ripple arcs under the fish, not overpowering the star
for rad in (1.05,1.55,2.05):
    add_torus('Calm water ripple %.1f'%rad,(0,0,.31),rad,.018,(0,0,0),ripple_mat,detail_col)

# ---------- watery backdrop ----------
# vertical plane facing the camera
verts=[(-10,5.8,-.15),(10,5.8,-.15),(10,5.8,10),(-10,5.8,10)]
back=mesh_obj('Calm blue water backdrop',verts,[(0,1,2,3)],back_mat,env_col,False)
# subtle vertical water-flow curves on the backdrop
for i,x in enumerate([-5.0,-3.8,3.8,5.1]):
    pts=[(x,5.70,.5),(x+.22*sin(i),5.68,2.5),(x-.18,5.69,4.8),(x+.24,5.68,7.1),(x,5.69,9.2)]
    create_curve('Backdrop current %02d'%i,pts,.018,ripple_mat,detail_col)
# stylized seaweed silhouettes at the back edges
for i,(x,y,h,lean) in enumerate([(-4.6,3.2,2.4,.45),(-4.1,3.35,1.8,-.35),(4.4,3.3,2.3,-.5),(4.9,3.45,1.6,.35)]):
    pts=[(x,y,.28),(x+lean*.25,y,1.0),(x-lean*.10,y,1.65),(x+lean,y, h)]
    create_curve('Seaweed frond %02d'%i,pts,.095,plant_mat,env_col)
# little frond branches
create_curve('Left seaweed branch',[(-4.55,3.18,1.25),(-4.9,3.18,1.65),(-5.0,3.18,1.95)],.045,plant_mat,env_col)
create_curve('Right seaweed branch',[(4.45,3.28,1.1),(4.85,3.28,1.45),(5.05,3.28,1.8)],.045,plant_mat,env_col)

# ---------- bubbles ----------
bubble_specs=[(-3.65,2.0,2.5,.22),(-3.28,2.2,3.15,.11),(-3.62,2.1,3.65,.075),(3.72,2.1,3.55,.25),(4.15,2.25,4.20,.12),(3.95,2.05,2.65,.10),(-4.45,2.4,4.55,.10)]
for i,(x,y,z,radius) in enumerate(bubble_specs):
    b=add_uv('Water bubble %02d'%i,(x,y,z),(radius,radius,radius),bubble_mat,env_col,24,16)
    # fine bright rim for legibility in the render
    add_torus('Bubble rim %02d'%i,(x-radius*.12,y-radius*.86,z+radius*.05),radius*.73,radius*.035,(pi/2,0,0),ripple_mat,detail_col)
    add_uv('Bubble highlight %02d'%i,(x-radius*.32,y-radius*.94,z+radius*.30),(radius*.13,radius*.035,radius*.13),white_mat,detail_col,16,10)

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

def add_area(name,loc,energy,size,color,target=(0,0,2.5)):
    dat=bpy.data.lights.new(name,'AREA'); dat.energy=energy; dat.shape='DISK'; dat.size=size; dat.color=color
    ob=bpy.data.objects.new(name,dat); light_col.objects.link(ob); ob.location=loc; look_at(ob,target); return ob
add_area('Warm golden key',(-5.5,-7.0,8.5),1150,5.0,(1.0,.33,.10),(0,0,2.8))
add_area('Cool cyan fill',(5.5,-3.0,5.5),850,4.5,(.08,.42,1.0),(.5,0,2.8))
add_area('Blue water rim',(3.0,5.2,7.0),1250,3.5,(.04,.32,1.0),(1.0,0,2.7))
add_area('Soft top sheen',(-1.0,1.0,10.0),900,4.0,(1.0,.65,.28),(0,0,2.5))
# gentle front sparkle
add_area('Front softbox',(-3.0,-6.0,4.0),480,3.0,(1.0,.62,.34),(-.7,0,2.7))

# ---------- camera ----------
cam_data=bpy.data.cameras.new('Hero camera'); cam=bpy.data.objects.new('Hero camera',cam_data); light_col.objects.link(cam)
cam.location=(-8.6,-14.2,6.4); cam_data.lens=56; cam_data.sensor_width=36
look_at(cam,(.38,.15,2.55)); cam_data.dof.use_dof=True; cam_data.dof.focus_object=body; cam_data.dof.aperture_fstop=8.0
bpy.context.scene.camera=cam

# ---------- world / render ----------
scene=bpy.context.scene
scene.world.color=(.002,.012,.025)
scene.world.use_nodes=True
wn=scene.world.node_tree; wn.nodes.clear(); wout=wn.nodes.new('ShaderNodeOutputWorld'); wbg=wn.nodes.new('ShaderNodeBackground'); wbg.inputs['Color'].default_value=(.002,.018,.04,1); wbg.inputs['Strength'].default_value=.28; wn.links.new(wbg.outputs['Background'],wout.inputs['Surface'])
scene.render.engine='BLENDER_EEVEE_NEXT'
scene.render.resolution_x=700; scene.render.resolution_y=700; scene.render.resolution_percentage=100
scene.render.image_settings.file_format='PNG'
scene.render.film_transparent=False
scene.render.filepath='/tmp/goldfish_preview.png'
# color management
try:
    scene.view_settings.look='AgX - Medium High Contrast'
except:
    try: scene.view_settings.look='Medium High Contrast'
    except: pass
# transparent / shadow tweaks
for ob in bpy.context.scene.objects:
    if ob.type=='MESH':
        # preserve nice normals and keep objects export-friendly
        pass
# organize naming metadata
scene['hero_subject']='Single goldfish with rounded S-curved body, flowing tail, dorsal and paired side fins, one eye on each side'
scene['floor_shape']='Five-point child-named star'
scene['visual_theme']='Calm underwater blue backdrop with warm orange translucency'
# select body as active for a friendly file opening
bpy.context.view_layer.objects.active=body; body.select_set(True)
# save working file
bpy.ops.wm.save_as_mainfile(filepath='/tmp/goldfish_scene.blend')
# render preview
bpy.ops.render.render(write_still=True)
result={'objects':len(bpy.data.objects),'meshes':len(bpy.data.meshes),'materials':len(bpy.data.materials),'preview':'/tmp/goldfish_preview.png','file':'/tmp/goldfish_scene.blend'}


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 252, in <module>
  File "<string>", line 193, in create_fin
  File "<string>", line 193, in <lambda>
AttributeError: Vector subtraction: (Vector - int) invalid type for this operation