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

# -----------------------------
# Clean scene and setup helpers
# -----------------------------
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
# Remove old custom collections while preserving the master scene collection
for c in list(bpy.data.collections):
    if c.users == 0:
        bpy.data.collections.remove(c)

def make_collection(name):
    c = bpy.data.collections.get(name) or bpy.data.collections.new(name)
    if c.name not in [ch.name for ch in scene.collection.children]:
        scene.collection.children.link(c)
    return c

COL_STUDIO = make_collection('STUDIO')
COL_BASE = make_collection('BASE_ASSEMBLY')
COL_JOINTS = make_collection('PRECISION_JOINTS')
COL_STRUTS = make_collection('TOPOLOGY_OPTIMIZED_STRUTS')
COL_TOOL = make_collection('PARALLEL_GRIPPER')
COL_PROPS = make_collection('DESK_OBJECTS')
COL_LIGHTS = make_collection('LIGHTING_RIG')
COL_GRAPHICS = make_collection('DESIGN_GRAPHICS')

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

def add_mat(name, color, metallic=0.0, roughness=0.35, transmission=0.0, alpha=1.0, emission=None):
    m = bpy.data.materials.get(name) or bpy.data.materials.new(name)
    m.use_nodes = True
    bsdf = m.node_tree.nodes.get('Principled BSDF')
    if bsdf:
        if bsdf.inputs.get('Base Color'): bsdf.inputs['Base Color'].default_value = (*color, 1.0)
        if bsdf.inputs.get('Metallic'): bsdf.inputs['Metallic'].default_value = metallic
        if bsdf.inputs.get('Roughness'): bsdf.inputs['Roughness'].default_value = roughness
        if bsdf.inputs.get('Transmission Weight'): bsdf.inputs['Transmission Weight'].default_value = transmission
        elif bsdf.inputs.get('Transmission'): bsdf.inputs['Transmission'].default_value = transmission
        if bsdf.inputs.get('Alpha'): bsdf.inputs['Alpha'].default_value = alpha
        if emission is not None:
            if bsdf.inputs.get('Emission Color'): bsdf.inputs['Emission Color'].default_value = (*emission, 1.0)
            elif bsdf.inputs.get('Emission'): bsdf.inputs['Emission'].default_value = (*emission, 1.0)
            if bsdf.inputs.get('Emission Strength'): bsdf.inputs['Emission Strength'].default_value = 0.35
    m.diffuse_color = (*color, alpha)
    # Blender 4.x uses surface_render_method for transparent materials; keep this guarded for portability.
    if alpha < 1.0:
        try: m.surface_render_method = 'DITHERED'
        except Exception: pass
        try: m.use_transparency_overlap = False
        except Exception: pass
    return m

MAT = {
    'desk': add_mat('Desk | warm neutral laminate', (0.30,0.33,0.36), metallic=0.05, roughness=0.30),
    'desk_edge': add_mat('Desk | edge graphite', (0.045,0.055,0.07), metallic=0.55, roughness=0.25),
    'wall': add_mat('Studio | neutral backdrop', (0.16,0.19,0.23), metallic=0.0, roughness=0.42),
    'base': add_mat('Base | graphite anodized', (0.025,0.043,0.065), metallic=0.78, roughness=0.22),
    'base2': add_mat('Base | satin navy housing', (0.045,0.11,0.15), metallic=0.65, roughness=0.24),
    'titanium': add_mat('Struts | topology titanium', (0.38,0.48,0.53), metallic=0.88, roughness=0.19),
    'titanium_dark': add_mat('Struts | shadow titanium', (0.08,0.16,0.19), metallic=0.83, roughness=0.22),
    'aluminum': add_mat('Joints | machined aluminum', (0.53,0.61,0.65), metallic=0.92, roughness=0.16),
    'steel': add_mat('Joints | dark precision steel', (0.035,0.055,0.072), metallic=0.9, roughness=0.20),
    'accent': add_mat('Accent | cyan calibration', (0.015,0.34,0.43), metallic=0.58, roughness=0.17),
    'accent2': add_mat('Accent | soft cyan edge', (0.03,0.55,0.62), metallic=0.35, roughness=0.20),
    'black': add_mat('Cables | controlled black', (0.003,0.006,0.009), metallic=0.22, roughness=0.28),
    'rubber': add_mat('Gripper | contact rubber', (0.008,0.012,0.016), metallic=0.1, roughness=0.35),
    'orange': add_mat('Test object | anodized orange', (0.78,0.15,0.025), metallic=0.68, roughness=0.22),
    'orange_dark': add_mat('Test object | shadow orange', (0.22,0.025,0.008), metallic=0.55, roughness=0.26),
    'membrane': add_mat('Technical membrane | translucent TPU', (0.03,0.33,0.41), metallic=0.08, roughness=0.13, transmission=0.42, alpha=0.42),
    'label': add_mat('Markings | cool white', (0.62,0.83,0.86), metallic=0.35, roughness=0.2, emission=(0.18,0.45,0.5)),
}

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

def bevel(obj, width=0.04, segments=3, angle=0.5):
    if width <= 0: return
    mod = obj.modifiers.new('Precision edge radii', 'BEVEL')
    mod.width = width
    mod.segments = segments
    mod.limit_method = 'ANGLE'
    mod.angle_limit = angle
    mod.harden_normals = True

def add_box(name, loc, dims, mat, bevel_width=0.04, rotation=None, coll=COL_STUDIO):
    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 rotation is not None:
        o.rotation_mode = 'QUATERNION'
        o.rotation_quaternion = rotation
    if mat: o.data.materials.append(mat)
    bevel(o, bevel_width, 3)
    set_smooth(o)
    link_to(o, coll)
    return o

def quat_z_to(direction):
    d = Vector(direction).normalized()
    return Vector((0,0,1)).rotation_difference(d)

def add_cyl(name, loc, radius, depth, mat, direction=(0,0,1), vertices=48, bevel_width=0.025, coll=COL_STUDIO):
    d = Vector(direction).normalized()
    bpy.ops.mesh.primitive_cylinder_add(vertices=vertices, radius=radius, depth=depth, location=Vector(loc))
    o = bpy.context.object
    o.name = name
    o.rotation_mode = 'QUATERNION'
    o.rotation_quaternion = quat_z_to(d)
    if mat: o.data.materials.append(mat)
    bevel(o, bevel_width, 3)
    set_smooth(o)
    link_to(o, coll)
    return o

def add_uv(name, loc, radius, mat, scale=(1,1,1), coll=COL_STUDIO):
    bpy.ops.mesh.primitive_uv_sphere_add(segments=32, ring_count=16, radius=radius, location=Vector(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)
    set_smooth(o)
    link_to(o, coll)
    return o

def add_torus(name, loc, major, minor, mat, direction=(0,0,1), coll=COL_STUDIO):
    bpy.ops.mesh.primitive_torus_add(major_radius=major, minor_radius=minor, major_segments=64, minor_segments=16, location=Vector(loc))
    o = bpy.context.object
    o.name = name
    o.rotation_mode = 'QUATERNION'
    o.rotation_quaternion = quat_z_to(direction)
    if mat: o.data.materials.append(mat)
    set_smooth(o)
    link_to(o, coll)
    return o

def tube_path(name, points, radii, mat, sides=14, coll=COL_STRUTS):
    pts = [Vector(p) for p in points]
    if len(pts) < 2: return None
    if len(radii) == 1: radii = list(radii)*len(pts)
    verts=[]; faces=[]
    for i,p in enumerate(pts):
        if i == 0: tangent = (pts[1]-p).normalized()
        elif i == len(pts)-1: tangent = (p-pts[i-1]).normalized()
        else: tangent = (pts[i+1]-pts[i-1]).normalized()
        ref = Vector((0,1,0))
        if abs(tangent.dot(ref)) > 0.92: ref = Vector((0,0,1))
        e2 = ref.cross(tangent).normalized()
        e3 = tangent.cross(e2).normalized()
        r = radii[i]
        for j in range(sides):
            a = 2*pi*j/sides
            q = p + r*(cos(a)*e2 + sin(a)*e3)
            verts.append(tuple(q))
    for i in range(len(pts)-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))
    # capped ends
    start_idx=len(verts); verts.append(tuple(pts[0]))
    end_idx=len(verts); verts.append(tuple(pts[-1]))
    for j in range(sides):
        faces.append((start_idx, (j+1)%sides, j))
        a=(len(pts)-1)*sides+j; b=(len(pts)-1)*sides+(j+1)%sides
        faces.append((end_idx, a, b))
    mesh=bpy.data.meshes.new(name+' | mesh')
    mesh.from_pydata(verts, [], faces); mesh.update()
    o=bpy.data.objects.new(name, mesh); coll.objects.link(o)
    if mat: mesh.materials.append(mat)
    set_smooth(o)
    return o

def add_cyl_between(name, p1, p2, radius, mat, vertices=24, bevel_width=0.018, coll=COL_STUDIO):
    a=Vector(p1); b=Vector(p2); d=b-a
    return add_cyl(name, (a+b)/2, radius, d.length, mat, d, vertices, bevel_width, coll)

def point_frame(p0, p1):
    p0=Vector(p0); p1=Vector(p1); d=(p1-p0).normalized()
    depth=Vector((0,1,0))
    if abs(d.dot(depth)) > 0.92: depth=Vector((0,0,1))
    width=depth.cross(d).normalized()
    depth=d.cross(width).normalized()
    return p0,d,width,depth,(p1-p0).length

# -----------------------------
# Topology-optimized link builder
# -----------------------------
def organic_strut(name, p0, p1, half_width=0.28, front_membrane=False, variant=0):
    p0,d,eW,eD,L = point_frame(p0,p1)
    def P(t,w=0,dep=0):
        return p0 + d*(L*t) + eW*w + eD*dep
    # sculpted rail profile: narrow roots, full section in the load span
    ts=[0.18,0.30,0.48,0.68,0.82]
    ws=[half_width*0.54, half_width*0.91, half_width*1.02, half_width*0.86, half_width*0.48]
    rr=[0.105,0.125,0.135,0.12,0.095]
    for depth_sign, label in [(-1,'front'),(1,'rear')]:
        dep=depth_sign*0.125
        tube_path(name+' | '+label+' upper load rail', [P(t,w,dep) for t,w in zip(ts,ws)], rr, MAT['titanium'], 16)
        tube_path(name+' | '+label+' lower load rail', [P(t,-w,dep) for t,w in zip(ts,ws)], rr, MAT['titanium'], 16)
        # Short end diaphragms keep the organic frame a real box section.
        tube_path(name+' | '+label+' root diaphragm', [P(.18,-ws[0],dep), P(.18,ws[0],dep)], [0.083,0.083], MAT['titanium'], 14)
        tube_path(name+' | '+label+' tip diaphragm', [P(.82,-ws[-1],dep), P(.82,ws[-1],dep)], [0.072,0.072], MAT['titanium'], 14)
    # Visible branching web: asymmetric diagonals create two large purposeful voids.
    front=-0.145
    tube_path(name+' | branching web A', [P(.22,ws[1]*.82,front), P(.49,-ws[2]*.92,front), P(.67,-ws[3]*.55,front)], [0.088,0.102,0.072], MAT['titanium_dark'], 14)
    tube_path(name+' | branching web B', [P(.28,-ws[1]*.68,front), P(.53,ws[2]*.92,front), P(.77,ws[3]*.62,front)], [0.075,0.095,0.064], MAT['accent'], 14)
    # Subtle rear diagonal, visible on orbit.
    rear=0.145
    tube_path(name+' | rear load branch', [P(.25,-ws[1]*.76,rear), P(.58,ws[2]*.82,rear), P(.76,-ws[3]*.45,rear)], [0.065,0.082,0.055], MAT['titanium_dark'], 13)
    # Root and tip Y-transitions: load flows from a solid interface into the open frame.
    tube_path(name+' | root upper transition', [P(.105,0,0), P(.19,ws[0]*.82,0)], [0.145,0.095], MAT['titanium'], 15)
    tube_path(name+' | root lower transition', [P(.105,0,0), P(.19,-ws[0]*.82,0)], [0.145,0.095], MAT['titanium'], 15)
    tube_path(name+' | tip upper transition', [P(.81,ws[-1]*.82,0), P(.895,0,0)], [0.09,0.14], MAT['titanium'], 15)
    tube_path(name+' | tip lower transition', [P(.81,-ws[-1]*.82,0), P(.895,0,0)], [0.09,0.14], MAT['titanium'], 15)
    # Depth ties are sparse, rather than a decorative repeated lattice.
    for k,t in enumerate((.34,.68)):
        w=half_width*(0.96 if t<.5 else .84)
        add_cyl_between(name+f' | depth tie {k+1} upper', P(t,w,-.12), P(t,w,.12), .045, MAT['titanium_dark'], 16, .01, COL_STRUTS)
        add_cyl_between(name+f' | depth tie {k+1} lower', P(t,-w,-.12), P(t,-w,.12), .045, MAT['titanium_dark'], 16, .01, COL_STRUTS)
    # Smooth node enlargements at branch roots make the topology feel grown, not bolted-on.
    for k,(t,w) in enumerate(((.30,ws[1]),(.48,-ws[2]),(.53,ws[2]),(.68,-ws[3]))):
        add_uv(name+f' | organic node {k+1}', P(t,w,front), .105, MAT['titanium'], scale=(1.10,0.88,1.0), coll=COL_STRUTS)
    # One restrained tensioned membrane panel. It occupies only one bay and leaves the rest open.
    if front_membrane:
        t0=.33; t1=.60
        w0=half_width*.80; w1=half_width*.93
        verts=[tuple(P(t0,-w0,-.16)), tuple(P(t1,-w1,-.16)), tuple(P(t1,w1,-.16)), tuple(P(t0,w0,-.16))]
        mesh=bpy.data.meshes.new(name+' | membrane mesh')
        mesh.from_pydata(verts, [], [(0,1,2,3)]); mesh.update()
        panel=bpy.data.objects.new(name+' | restrained TPU membrane', mesh); COL_STRUTS.objects.link(panel)
        mesh.materials.append(MAT['membrane'])
        # a very fine perimeter seam, integrated close to the frame
        tube_path(name+' | membrane upper seam', [P(t0,w0,-.165),P((t0+t1)/2,half_width*.90,-.165),P(t1,w1,-.165)], [0.018,0.018,0.018], MAT['accent2'], 8)
        tube_path(name+' | membrane lower seam', [P(t0,-w0,-.165),P((t0+t1)/2,-half_width*.90,-.165),P(t1,-w1,-.165)], [0.018,0.018,0.018], MAT['accent2'], 8)
    return {'p0':Vector(p0),'p1':Vector(p1),'d':d,'L':L}

# -----------------------------
# Studio desk and backdrop
# -----------------------------
add_box('Desk | seamless worktop', (1.8,0,-0.20), (12.0,7.2,0.40), MAT['desk'], 0.14, coll=COL_STUDIO)
add_box('Desk | front structural edge', (1.8,-3.53,-0.42), (12.0,0.16,0.52), MAT['desk_edge'], 0.035, coll=COL_STUDIO)
add_box('Studio | rear wall', (1.8,3.55,3.9), (12.0,0.28,8.2), MAT['wall'], 0.12, coll=COL_STUDIO)
# a dark inset service mat gives the base a believable replaceable mounting surface
add_box('Base | replaceable service mat', (0.0,0.0,0.035), (2.85,2.35,0.065), MAT['steel'], 0.08, coll=COL_BASE)

# -----------------------------
# Base: stable mechanical assembly with hardware
# -----------------------------
add_box('Base | machined footprint plate', (0,0,0.19), (2.35,1.85,0.30), MAT['base'], 0.11, coll=COL_BASE)
add_box('Base | upper mounting plate', (0,0,0.385), (1.86,1.43,0.12), MAT['aluminum'], 0.055, coll=COL_BASE)
# mounting washers, socket heads, and subtle contrasting bolt centers
for i,(x,y) in enumerate(((-.84,-.61),(.84,-.61),(-.84,.61),(.84,.61))):
    add_cyl(f'Base | washer {i+1}', (x,y,0.48), .125, .035, MAT['steel'], (0,0,1), 48, .008, COL_BASE)
    add_cyl(f'Base | mounting socket {i+1}', (x,y,0.535), .064, .095, MAT['aluminum'], (0,0,1), 6, .008, COL_BASE)
    add_cyl(f'Base | socket insert {i+1}', (x,y,0.592), .028, .012, MAT['black'], (0,0,1), 32, .003, COL_BASE)
# rotating platform and bearing ring
add_cyl('Base | rotating platform drum', (0,0,0.62), .76, .34, MAT['base2'], (0,0,1), 64, .045, COL_BASE)
add_cyl('Base | rotating platform top', (0,0,0.805), .62, .075, MAT['aluminum'], (0,0,1), 64, .025, COL_BASE)
add_torus('Base | exposed bearing race', (0,0,0.86), .565, .042, MAT['accent'], (0,0,1), COL_BASE)
add_torus('Base | lower seam', (0,0,0.47), .69, .026, MAT['steel'], (0,0,1), COL_BASE)
add_cyl('Base | central rotary shaft', (0,0,0.91), .18, .16, MAT['steel'], (0,0,1), 48, .018, COL_BASE)
add_cyl('Base | rotary shaft cap', (0,0,1.015), .13, .055, MAT['accent'], (0,0,1), 48, .012, COL_BASE)
# fixed vertical pedestal
add_cyl('Base | vertical pedestal', (0,0,1.43), .39, 1.02, MAT['base2'], (0,0,1), 64, .05, COL_BASE)
add_torus('Base | pedestal lower collar', (0,0,0.98), .36, .045, MAT['aluminum'], (0,0,1), COL_BASE)
add_torus('Base | pedestal upper collar', (0,0,1.91), .35, .042, MAT['accent'], (0,0,1), COL_BASE)
# two clean side cover plates on the pedestal
add_box('Base | left service cover', (-.355,-.02,1.48), (.06,.52,.52), MAT['steel'], .025, coll=COL_BASE)
add_box('Base | right service cover', (.355,-.02,1.48), (.06,.52,.52), MAT['steel'], .025, coll=COL_BASE)

# -----------------------------
# Articulated layout
# -----------------------------
J0=Vector((0,0,0.92))
J1=Vector((0,0,2.10))
J2=Vector((1.78,0.02,3.48))
J3=Vector((3.42,-0.01,2.42))
J4=Vector((4.13,-0.10,1.86))

# Shoulder and upper joint mechanical body
# Axis y makes the pitch axes obvious from the presentation angle.
def rotary_joint(name, center, axis, radius, depth, body_mat, face_mat, accent_mat, bolt_count=6, coll=COL_JOINTS):
    c=Vector(center); a=Vector(axis).normalized()
    add_cyl(name+' | cylindrical gearbox', c, radius, depth, body_mat, a, 64, .035, coll)
    front=c-a*(depth/2+0.018)
    back=c+a*(depth/2+0.018)
    add_cyl(name+' | front machined flange', front, radius*.82, .075, face_mat, a, 64, .018, coll)
    add_cyl(name+' | rear cover', back, radius*.78, .07, body_mat, a, 64, .018, coll)
    add_torus(name+' | flange retaining ring', front-a*.045, radius*.69, .032, accent_mat, a, coll)
    add_cyl(name+' | visible axis hub', front-a*.06, radius*.26, .11, accent_mat, a, 48, .014, coll)
    # bolt circle in the flange plane
    b1=Vector((1,0,0))
    if abs(a.dot(b1))>.92: b1=Vector((0,1,0))
    b1=(b1-a*b1.dot(a)).normalized(); b2=a.cross(b1).normalized()
    for i in range(bolt_count):
        ang=2*pi*i/bolt_count
        pos=front-a*.052 + (b1*cos(ang)+b2*sin(ang))*(radius*.61)
        add_cyl(name+f' | flange bolt {i+1}', pos, radius*.065, .095, MAT['black'], a, 20, .008, coll)
    return c,a

rotary_joint('Shoulder J1',J1,(0,1,0),.46,.78,MAT['steel'],MAT['aluminum'],MAT['accent'],6)
# compact actuator cover mounted behind the shoulder, not part of the generative link
add_box('Shoulder J1 | servo cover', (-.24,0.34,2.23), (.62,.34,.68), MAT['base2'], .10, coll=COL_JOINTS)
add_cyl('Shoulder J1 | actuator end cap', (-.24,0.16,2.23), .17, .08, MAT['accent'], (0,1,0), 48, .012, COL_JOINTS)

# Organic structural links: only these elongated members use branching topology.
S1=organic_strut('Link 1 | upper arm optimized member',J1,J2,.31,front_membrane=True,variant=0)
rotary_joint('Elbow J2',J2,(0,1,0),.40,.72,MAT['steel'],MAT['aluminum'],MAT['accent2'],6)
# elbow clevis / gearbox block on the visible side
add_box('Elbow J2 | actuator module', (1.78,-.39,3.62), (.54,.25,.46), MAT['base2'], .075, coll=COL_JOINTS)
add_cyl('Elbow J2 | actuator face', (1.78,-.535,3.62), .13, .055, MAT['accent'], (0,1,0), 48, .01, COL_JOINTS)

S2=organic_strut('Link 2 | forearm optimized member',J2,J3,.275,front_membrane=False,variant=1)
rotary_joint('Wrist Pitch J3',J3,(0,1,0),.32,.60,MAT['steel'],MAT['aluminum'],MAT['accent2'],6)
add_box('Wrist J3 | compact gearbox', (3.42,-.34,2.53), (.40,.22,.34), MAT['base2'], .06, coll=COL_JOINTS)
add_cyl('Wrist J3 | actuator face', (3.42,-.47,2.53), .095, .05, MAT['accent'], (0,1,0), 40, .008, COL_JOINTS)

S3=organic_strut('Link 3 | wrist optimized member',J3,J4,.205,front_membrane=False,variant=2)
# Wrist roll axis follows the terminal link, a believable tool-roll joint.
tool_axis=(J4-J3).normalized()
rotary_joint('Wrist Roll J4',J4,tool_axis,.245,.52,MAT['steel'],MAT['aluminum'],MAT['accent'],5)
# Replaceable interface plate at tool side of roll joint
iface_center=J4+tool_axis*.31
add_cyl('Tool interface | replaceable plate', iface_center, .23, .095, MAT['aluminum'], tool_axis, 64, .018, COL_JOINTS)
add_torus('Tool interface | retaining ring', iface_center+tool_axis*.055, .18, .022, MAT['accent2'], tool_axis, COL_JOINTS)
# Interface plate fasteners (four visible countersunk-style heads)
b1=Vector((0,1,0))
if abs(b1.dot(tool_axis))>.9: b1=Vector((1,0,0))
b1=(b1-tool_axis*b1.dot(tool_axis)).normalized(); b2=tool_axis.cross(b1).normalized()
for i in range(4):
    ang=pi/4 + i*pi/2
    pos=iface_center+tool_axis*.065+(b1*cos(ang)+b2*sin(ang))*.145
    add_cyl(f'Tool interface | fastener {i+1}',pos,.026,.055,MAT['black'],tool_axis,20,.005,COL_JOINTS)

# -----------------------------
# Minimal controlled cable routing
# -----------------------------
# One restrained cable stays close to the rear/underside of the members.
def cable_point(Pfun,t,offset):
    return Pfun(t,offset[0],offset[1])
# Build cable paths alongside each link with small molded clamps.
def add_link_cable(name,p0,p1,offset_w,offset_d):
    p0,d,eW,eD,L=point_frame(p0,p1)
    pts=[]
    for t in (.12,.34,.58,.80,.91):
        pts.append(p0+d*(L*t)+eW*offset_w+eD*offset_d)
    tube_path(name,pts,[.026]*len(pts),MAT['black'],10,COL_JOINTS)
    for i,t in enumerate((.34,.80)):
        pos=pts[1] if i==0 else pts[3]
        add_torus(name+f' | molded clamp {i+1}',pos,.055,.018,MAT['black'],d,COL_JOINTS)
add_link_cable('Cable route | upper arm',J1,J2,-.22,-.18)
add_link_cable('Cable route | forearm',J2,J3,-.20,-.18)
add_link_cable('Cable route | wrist',J3,J4,-.15,-.17)
# short protected loop at the tool interface
cable_pts=[J4-tool_axis*.18+Vector((0,-.18,.03)), J4+tool_axis*.05+Vector((0,-.19,.04)), iface_center-tool_axis*.08+Vector((0,-.17,.02))]
tube_path('Cable route | wrist-to-tool protected loop',cable_pts,[.024,.024,.022],MAT['black'],10,COL_JOINTS)

# -----------------------------
# Refined two-finger parallel gripper
# -----------------------------
tool_dir=tool_axis
# Keep the opening axis nearly horizontal for a readable parallel-jaw silhouette.
open_dir=Vector((1,0,0))
# make open_dir perpendicular enough to tool direction
open_dir=(open_dir-tool_dir*open_dir.dot(tool_dir)).normalized()
depth_dir=tool_dir.cross(open_dir).normalized()
# A mount neck between replaceable interface and gripper body
neck_center=iface_center+tool_dir*.22
add_cyl('Gripper | mount neck',neck_center,.12,.38,MAT['steel'],tool_dir,48,.018,COL_TOOL)
body_center=iface_center+tool_dir*.47
rot=quat_z_to(tool_dir)
add_box('Gripper | compact parallel jaw body',body_center,(.72,.30,.20),MAT['aluminum'],.055,rotation=rot,coll=COL_TOOL)
# rear motor block / lead screw cover
add_box('Gripper | lead-screw actuator',body_center-tool_dir*.14+depth_dir*.015,(.38,.28,.24),MAT['base2'],.05,rotation=rot,coll=COL_TOOL)
add_cyl('Gripper | actuator spindle',body_center-tool_dir*.22,.07,.28,MAT['accent'],tool_dir,40,.012,COL_TOOL)
# two solid fingers with visible pivot bosses and replaceable pads
for side,sgn in enumerate((-1,1),1):
    jaw_center=body_center+open_dir*(sgn*.235)+tool_dir*.28
    add_box(f'Gripper | solid finger {side}',jaw_center,(.145,.22,.53),MAT['titanium'],.035,rotation=rot,coll=COL_TOOL)
    # front-facing pivot boss near root; depth direction is roughly y/front
    piv=body_center+open_dir*(sgn*.235)+depth_dir*.145+tool_dir*.08
    add_cyl(f'Gripper | finger pivot {side}',piv,.055,.06,MAT['accent2'],depth_dir,32,.008,COL_TOOL)
    add_cyl(f'Gripper | pivot socket {side}',piv+depth_dir*.035,.022,.018,MAT['black'],depth_dir,24,.003,COL_TOOL)
    pad_center=body_center+open_dir*(sgn*.235)+tool_dir*.585
    add_box(f'Gripper | replaceable contact pad {side}',pad_center,(.18,.24,.105),MAT['rubber'],.022,rotation=rot,coll=COL_TOOL)
    # small cyan pad insert on inner jaw face
    insert_center=pad_center-open_dir*(sgn*.082)
    add_box(f'Gripper | pad insert {side}',insert_center,(.018,.18,.07),MAT['accent'],.008,rotation=rot,coll=COL_TOOL)
# crossbar at jaw roots reinforces parallel motion
add_cyl_between('Gripper | jaw guide rail',body_center-open_dir*.29,body_center+open_dir*.29,.045,MAT['steel'],24,.01,COL_TOOL)

# -----------------------------
# Small machined calibration object on desk
# -----------------------------
obj=Vector((4.22,-0.76,0.0))
add_cyl('Target | replaceable rubber locator',obj+Vector((0,0,.055)),.34,.10,MAT['black'],(0,0,1),64,.018,COL_PROPS)
add_cyl('Target | machined calibration puck',obj+Vector((0,0,.22)),.245,.33,MAT['orange'],(0,0,1),64,.035,COL_PROPS)
add_torus('Target | lower precision groove',obj+Vector((0,0,.095)),.235,.018,MAT['orange_dark'],(0,0,1),COL_PROPS)
add_torus('Target | upper precision groove',obj+Vector((0,0,.355)),.195,.016,MAT['aluminum'],(0,0,1),COL_PROPS)
add_cyl('Target | top insert',obj+Vector((0,0,.405)),.145,.055,MAT['aluminum'],(0,0,1),48,.012,COL_PROPS)
add_cyl('Target | top datum button',obj+Vector((0,0,.45)),.052,.035,MAT['accent'],(0,0,1),40,.008,COL_PROPS)
# a restrained target-zone ring on the worktop
add_torus('Target | placement datum ring',obj+Vector((0,0,.026)),.43,.012,MAT['accent'],(0,0,1),COL_PROPS)

# -----------------------------
# Small design label on front of base
# -----------------------------
def add_label(name, body, loc, size, mat, coll=COL_GRAPHICS):
    cu=bpy.data.curves.new(name+' | curve','FONT')
    cu.body=body; cu.align_x='CENTER'; cu.align_y='CENTER'; cu.size=size; cu.extrude=.004; cu.bevel_depth=.0015; cu.resolution_u=8
    ob=bpy.data.objects.new(name,cu); coll.objects.link(ob); ob.location=loc; ob.rotation_euler=(pi/2,0,0)
    cu.materials.append(mat)
    # Convert for reliable GLB inspection.
    bpy.context.view_layer.objects.active=ob; ob.select_set(True)
    try: bpy.ops.object.convert(target='MESH')
    except Exception: pass
    ob.select_set(False)
    return ob
add_label('Base marking | GEN-04', 'GEN-04', (0,-.952,.22), .22, MAT['label'])
add_label('Base marking | TOPOLOGY RESEARCH', 'TOPOLOGY / 04', (0,-.954,.13), .095, MAT['label'])

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

def add_area(name, loc, energy, size, color, target):
    data=bpy.data.lights.new(name,'AREA'); data.energy=energy; data.shape='DISK'; data.size=size; data.color=color
    ob=bpy.data.objects.new(name,data); COL_LIGHTS.objects.link(ob); ob.location=loc; look_at(ob,target)
    return ob

def add_point(name, loc, energy, color):
    data=bpy.data.lights.new(name,'POINT'); data.energy=energy; data.color=color; data.shadow_soft_size=.25
    ob=bpy.data.objects.new(name,data); COL_LIGHTS.objects.link(ob); ob.location=loc
    return ob

# Large soft key, cool fill, and a rim that catches the optimized rails.
add_area('Key | large softbox', (2.2,-5.8,9.5), 1150, 5.0, (1.0,.88,.76), (1.7,0,2.0))
add_area('Fill | cool studio card', (-5.0,-2.8,5.4), 850, 4.0, (.62,.78,1.0), (1.3,0,2.2))
add_area('Rim | cyan edge light', (3.8,4.8,7.0), 1350, 3.2, (.43,.75,1.0), (2.4,0,2.2))
add_area('Top | controlled highlight', (7.0,-.5,10.5), 900, 3.0, (1.0,1.0,1.0), (2.2,0,1.8))
add_point('Object | small focus light', (4.2,-1.8,3.1), 80, (1.0,.35,.12))

bpy.ops.object.camera_add(location=(9.0,-14.5,7.4))
cam=bpy.context.object; cam.name='Presentation Camera | 3/4 product view'; cam.data.lens=55; cam.data.sensor_width=36
look_at(cam,(1.95,-0.05,2.12)); scene.camera=cam; link_to(cam,COL_LIGHTS)

# Render and color management: final benchmark will override resolution and engine, but the saved scene is Cycles-ready.
scene.render.engine='BLENDER_EEVEE_NEXT'
scene.render.resolution_x=1536; scene.render.resolution_y=1536; scene.render.resolution_percentage=100
scene.render.image_settings.file_format='PNG'
scene.render.film_transparent=False
scene.render.image_settings.color_mode='RGBA'
scene.world.color=(0.018,0.024,0.035)
if scene.world.use_nodes:
    bg=scene.world.node_tree.nodes.get('Background')
    if bg:
        bg.inputs['Color'].default_value=(0.018,0.024,0.035,1)
        bg.inputs['Strength'].default_value=.32
try: scene.view_settings.look='AgX - Medium High Contrast'
except Exception: pass
# Cycles final settings are stored in the blend; use Eevee only for the quick construction preview below.
scene['design_notes']='Experimental desk-mounted research arm: open topology-optimized struts are restricted to the three elongated links; all joints, base, wrist and gripper are conventional precision assemblies.'
scene['joint_count']=5
scene['presentation']='3/4 studio product view toward a machined calibration puck'

# Save a complete scene to the shared output path.
out='/home/oai/share/robotic_arm_topology_lab.blend'
os.makedirs(os.path.dirname(out), exist_ok=True)
bpy.ops.wm.save_as_mainfile(filepath=out)

# Quick preview render in Eevee for composition; final file will be switched to Cycles after preview.
scene.render.resolution_x=720; scene.render.resolution_y=720; scene.render.resolution_percentage=100
scene.render.filepath='/home/oai/share/robotic_arm_preview.png'
bpy.ops.render.render(write_still=True)
# Restore benchmark-facing resolution and Cycles configuration before final save.
scene.render.resolution_x=1536; scene.render.resolution_y=1536; scene.render.resolution_percentage=100
try:
    scene.render.engine='CYCLES'
    scene.cycles.samples=64
    scene.cycles.use_denoising=True
    scene.cycles.max_bounces=8
except Exception:
    pass
bpy.ops.wm.save_as_mainfile(filepath=out)

result={'objects':len(bpy.data.objects),'meshes':len(bpy.data.meshes),'materials':len(bpy.data.materials),'camera':cam.name,'blend':out,'preview':'/home/oai/share/robotic_arm_preview.png'}


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 466, in <module>
TypeError: bpy_struct: item.attr = val: enum "BLENDER_EEVEE_NEXT" not found in ('BLENDER_EEVEE', 'BLENDER_WORKBENCH', 'CYCLES')