```python
import bpy, math, os
from math import sin, cos, pi, radians, atan2
from mathutils import Vector, Matrix

# ============================================================
# SCENE RESET / RENDER SETUP
# ============================================================
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
scene = bpy.context.scene
scene.render.engine = 'CYCLES'
scene.cycles.samples = 64
scene.cycles.use_denoising = True
scene.render.resolution_x = 1536
scene.render.resolution_y = 1536
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = 'PNG'
scene.unit_settings.system = 'METRIC'
try:
    scene.view_settings.look = 'AgX - Medium High Contrast'
except Exception:
    pass

# ============================================================
# MATERIALS
# ============================================================
def mat_principled(name, color, metallic=0.0, rough=0.4, transmission=0.0,
                   ior=1.45, emission=None, estr=0.0, coat=0.0):
    m = bpy.data.materials.new(name); m.use_nodes = True
    bs = m.node_tree.nodes['Principled BSDF']
    bs.inputs['Base Color'].default_value = (*color, 1.0)
    bs.inputs['Metallic'].default_value = metallic
    bs.inputs['Roughness'].default_value = rough
    if transmission > 0:
        bs.inputs['Transmission Weight'].default_value = transmission
        bs.inputs['IOR'].default_value = ior
    if coat > 0:
        bs.inputs['Coat Weight'].default_value = coat
    if emission is not None:
        bs.inputs['Emission Color'].default_value = (*emission, 1.0)
        bs.inputs['Emission Strength'].default_value = estr
    return m

titanium = mat_principled('Strut_Titanium', (0.60, 0.61, 0.64), 1.0, 0.34)
alu      = mat_principled('Machined_Aluminum', (0.78, 0.79, 0.81), 1.0, 0.22)
alu_dark = mat_principled('Anodized_Dark', (0.15, 0.16, 0.18), 0.9, 0.32)
steel    = mat_principled('Steel', (0.42, 0.43, 0.46), 1.0, 0.18)
accent   = mat_principled('Accent_Orange', (0.82, 0.26, 0.045), 0.85, 0.30)
cablem   = mat_principled('Cable_Black', (0.012, 0.013, 0.015), 0.0, 0.55)
padm     = mat_principled('Pad_Urethane', (0.85, 0.38, 0.06), 0.0, 0.5)
deskm    = mat_principled('Desk_Laminate', (0.80, 0.79, 0.76), 0.0, 0.38)
floorm   = mat_principled('Studio_Floor', (0.66, 0.66, 0.68), 0.0, 0.58)
boxm     = mat_principled('Controller_Housing', (0.05, 0.05, 0.06), 0.3, 0.42)
ledg     = mat_principled('LED_Green', (0.02, 0.08, 0.03), 0, 0.4, emission=(0.15, 1.0, 0.35), estr=9.0)
leda     = mat_principled('LED_Amber', (0.08, 0.04, 0.01), 0, 0.4, emission=(1.0, 0.5, 0.1), estr=9.0)

# translucent technical membrane (clear reinforced film / TPU)
memb = mat_principled('Membrane_TPU', (0.72, 0.78, 0.82), 0.0, 0.34,
                      transmission=0.9, ior=1.38, coat=0.25)
_nt = memb.node_tree
_bs = _nt.nodes['Principled BSDF']
_noise = _nt.nodes.new('ShaderNodeTexNoise')
_noise.inputs['Scale'].default_value = 320.0
_noise.inputs['Detail'].default_value = 2.0
_bump = _nt.nodes.new('ShaderNodeBump')
_bump.inputs['Strength'].default_value = 0.12
_bump.inputs['Distance'].default_value = 0.00025
_nt.links.new(_noise.outputs['Fac'], _bump.inputs['Height'])
_nt.links.new(_bump.outputs['Normal'], _bs.inputs['Normal'])

# ============================================================
# GEOMETRY HELPERS
# ============================================================
def frame_from_axis(axis):
    z = Vector(axis).normalized()
    t = Vector((0, 0, 1)) if abs(z.z) < 0.95 else Vector((1, 0, 0))
    x = z.cross(t).normalized()
    y = z.cross(x).normalized()
    return x, y, z

def set_frame(obj, x, y, z, loc):
    M = Matrix((x, y, z)).transposed().to_4x4()
    M.translation = Vector(loc)
    obj.matrix_world = M

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

def cyl(name, r, depth, loc, axis=(0, 0, 1), mat=None, verts=48, bevel=0.0, smooth=True):
    bpy.ops.mesh.primitive_cylinder_add(vertices=verts, radius=r, depth=depth, location=loc)
    o = bpy.context.active_object; o.name = name
    x, y, z = frame_from_axis(axis)
    set_frame(o, x, y, z, loc)
    assign(o, mat)
    if smooth:
        for p in o.data.polygons:
            p.use_smooth = (len(p.vertices) == 4)
    if bevel > 0:
        m = o.modifiers.new('bev', 'BEVEL'); m.width = bevel; m.segments = 3; m.limit_method = 'ANGLE'
    return o

def box(name, dims, loc, frame=None, mat=None, bevel=0.0):
    bpy.ops.mesh.primitive_cube_add(size=1, location=loc)
    o = bpy.context.active_object; o.name = name
    o.dimensions = dims
    bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
    if frame:
        set_frame(o, frame[0], frame[1], frame[2], loc)
    assign(o, mat)
    if bevel > 0:
        m = o.modifiers.new('bev', 'BEVEL'); m.width = bevel; m.segments = 3; m.limit_method = 'ANGLE'
    return o

def torus(name, R, r, loc, axis=(0, 0, 1), mat=None):
    bpy.ops.mesh.primitive_torus_add(major_radius=R, minor_radius=r,
                                     major_segments=48, minor_segments=12, location=loc)
    o = bpy.context.active_object; o.name = name
    x, y, z = frame_from_axis(axis)
    set_frame(o, x, y, z, loc)
    assign(o, mat)
    for p in o.data.polygons: p.use_smooth = True
    return o

def bolt(name, p, axis, r, h, mat=steel, washer=True):
    a = Vector(axis).normalized(); P = Vector(p)
    if washer:
        cyl(name + '_w', r * 1.6, h * 0.30, P + a * (h * 0.15), axis, mat, verts=24)
    cyl(name + '_h', r, h, P + a * (h * 0.30 + h * 0.5), axis, mat, verts=6, smooth=False)

def bolt_ring(name, center, axis, ring_r, n, r, h, mat=steel, phase=0.0):
    x, y, z = frame_from_axis(axis)
    C = Vector(center)
    for k in range(n):
        a = phase + 2 * pi * k / n
        p = C + x * (ring_r * cos(a)) + y * (ring_r * sin(a))
        bolt('%s_%d' % (name, k), p, axis, r, h, mat, washer=False)

def rod(name, p1, p2, r, mat=steel, verts=12):
    d = Vector(p2) - Vector(p1)
    return cyl(name, r, d.length, (Vector(p1) + Vector(p2)) / 2, axis=d.normalized(),
               mat=mat, verts=verts)

def cable(name, pts, r, mat=cablem):
    cu = bpy.data.curves.new(name, 'CURVE'); cu.dimensions = '3D'
    cu.resolution_u = 16; cu.bevel_depth = r; cu.bevel_resolution = 3
    sp = cu.splines.new('BEZIER'); sp.bezier_points.add(len(pts) - 1)
    for bp, co in zip(sp.bezier_points, pts):
        bp.co = co; bp.handle_left_type = 'AUTO'; bp.handle_right_type = 'AUTO'
    ob = bpy.data.objects.new(name, cu)
    bpy.context.scene.collection.objects.link(ob)
    assign(ob, mat)
    return ob

def led(name, p, axis, mat, r=0.0022):
    a = Vector(axis).normalized(); P = Vector(p)
    cyl(name + '_bz', r * 1.7, 0.0018, P - a * 0.0004, axis, alu_dark, verts=24)
    cyl(name + '_c', r, 0.0014, P + a * 0.0009, axis, mat, verts=16)

def look_at(ob, target):
    d = Vector(target) - ob.location
    ob.rotation_euler = d.to_track_quat('-Z', 'Y').to_euler()

# ============================================================
# TOPOLOGY-OPTIMIZED STRUT BUILDER (skin + subsurf, organic)
# ============================================================
def optimized_strut(name, A, B, dA, dB, rA, rB, bend, n, mat):
    A = Vector(A); B = Vector(B)
    u = (B - A); L = u.length; u.normalize()
    v = Vector((0.0, 1.0, 0.0))
    w = u.cross(v).normalized()
    v = w.cross(u).normalized()
    def prof(t): return 0.60 + 0.55 * (abs(cos(pi * t)) ** 0.75)
    def hfun(t): return (dA + (dB - dA) * t) * 0.5 * prof(t)
    def rfun(t): return (rA + (rB - rA) * t) * (1.30 - 0.50 * sin(pi * t))
    def center(t): return A + u * (L * t) + w * (bend * sin(pi * t))
    verts = []; edges = []
    for i in range(n + 1):
        t = i / n; C = center(t); h = hfun(t)
        verts.append(C + w * h); verts.append(C - w * h)
    for i in range(n):
        a = 2 * i
        edges.append((a, a + 2)); edges.append((a + 1, a + 3))
    edges.append((0, 1)); edges.append((2 * n, 2 * n + 1))
    diag = set()
    for i in range(n):
        a = 2 * i
        diag.add((a, a + 3) if i % 2 == 0 else (a + 1, a + 2))
    mid = n // 2; a = 2 * mid
    diag.add((a, a + 3)); diag.add((a + 1, a + 2))   # X-braced middle bay
    for e in diag:
        edges.append((min(e), max(e)))
    radii = []
    for i in range(n + 1):
        r = rfun(i / n); radii += [r, r]
    me = bpy.data.meshes.new(name)
    me.from_pydata([tuple(p) for p in verts], edges, [])
    ob = bpy.data.objects.new(name, me)
    bpy.context.scene.collection.objects.link(ob)
    sk = ob.modifiers.new('Skin', 'SKIN')
    bpy.context.view_layer.update()
    sv = me.skin_vertices[0].data
    for i, d in enumerate(sv):
        d.radius = (radii[i], radii[i])
    sv[0].use_root = True
    bpy.ops.object.select_all(action='DESELECT')
    ob.select_set(True); bpy.context.view_layer.objects.active = ob
    bpy.ops.object.modifier_apply(modifier=sk.name)
    for p in me.polygons: p.use_smooth = True
    sub = ob.modifiers.new('Sub', 'SUBSURF'); sub.levels = 2; sub.render_levels = 2
    me.materials.append(mat)
    tops = [verts[2 * i] for i in range(n + 1)]
    bots = [verts[2 * i + 1] for i in range(n + 1)]
    return dict(obj=ob, u=u, v=v, w=w, A=A, L=L, n=n, tops=tops, bots=bots,
                center=center, hfun=hfun, rfun=rfun)

def membrane(name, st, bay, mat, bow=0.0028, nx=8, ny=8, inset=0.07):
    tops = st['tops']; bots = st['bots']; nrm = Vector(st['v'])
    c00 = bots[bay]; c10 = bots[bay + 1]; c11 = tops[bay + 1]; c01 = tops[bay]
    C = (c00 + c10 + c11 + c01) / 4
    P = [c + (C - c) * inset for c in (c00, c10, c11, c01)]
    verts = []; faces = []
    for j in range(ny + 1):
        s = j / ny
        for i in range(nx + 1):
            r = i / nx
            p = (1 - r) * (1 - s) * P[0] + r * (1 - s) * P[1] + r * s * P[2] + (1 - r) * s * P[3]
            p = p + nrm * (bow * sin(pi * r) * sin(pi * s))
            verts.append(tuple(p))
    for j in range(ny):
        for i in range(nx):
            a = j * (nx + 1) + i
            faces.append((a, a + 1, a + nx + 2, a + nx + 1))
    me = bpy.data.meshes.new(name)
    me.from_pydata(verts, [], faces)
    ob = bpy.data.objects.new(name, me)
    bpy.context.scene.collection.objects.link(ob)
    for p in me.polygons: p.use_smooth = True
    sol = ob.modifiers.new('Solid', 'SOLIDIFY'); sol.thickness = 0.0011; sol.offset = 0.0
    me.materials.append(mat)
    return ob

def cable_clip(name, st, t, off, mat=alu_dark):
    u = st['u']; v = st['v']; w = st['w']
    C = st['center'](t); surf = st['hfun'](t) + st['rfun'](t)
    depth = off - surf + 0.006
    loc = C - w * ((surf + off) / 2)
    return box(name, (0.012, 0.020, depth), loc, frame=(u, v, w), mat=mat, bevel=0.002)

def scpts(st, off, ts, voff):
    return [st['center'](t) - st['w'] * off + st['v'] * voff for t in ts]

# ============================================================
# KINEMATICS (joint layout)
# ============================================================
S = Vector((0.0, 0.0, 0.165))                       # shoulder (J2)
a1 = radians(48.0)
u1 = Vector((sin(a1), 0, cos(a1)))
L1 = 0.30
E = S + u1 * L1                                     # elbow (J3)
W = Vector((0.425, 0.0, 0.165))                     # wrist pitch (J4)
_d = W - E; L2 = _d.length; u2 = _d / L2
th2 = atan2(u2.z, u2.x); th3 = th2 - radians(28.0)
u3 = Vector((cos(th3), 0, sin(th3)))                # tool axis
v = Vector((0, 1, 0))
w1 = u1.cross(v).normalized()
w2 = u2.cross(v).normalized()
w3 = u3.cross(v).normalized()
F = W + u3 * 0.130                                  # fingertip reference
obj_x = F.x + u3.x * 0.018

# ============================================================
# BASE ASSEMBLY (J1 rotary platform)
# ============================================================
box('BasePlate', (0.17, 0.17, 0.014), (0, 0, 0.007), mat=alu_dark, bevel=0.008)
for sx in (-1, 1):
    for sy in (-1, 1):
        bolt('BaseBolt', (0.072 * sx, 0.072 * sy, 0.014), (0, 0, 1), 0.0045, 0.005)
cyl('BaseLower', 0.062, 0.032, (0, 0, 0.030), mat=alu, bevel=0.004)
cyl('BaseBearingRing', 0.056, 0.012, (0, 0, 0.052), mat=alu_dark, bevel=0.002)
cyl('BaseAccentRing', 0.0568, 0.0022, (0, 0, 0.0575), mat=accent)
cyl('Turret', 0.050, 0.036, (0, 0, 0.076), mat=alu, bevel=0.004)
cyl('TurretTopPlate', 0.055, 0.008, (0, 0, 0.098), mat=alu_dark, bevel=0.002)
bolt_ring('TurretBolt', (0, 0, 0.102), (0, 0, 1), 0.043, 6, 0.0028, 0.0035)
led('TurretLED', (0, -0.0495, 0.072), (0, -1, 0), ledg)
box('TurretTick', (0.005, 0.0014, 0.006), (0, -0.0498, 0.085), mat=accent, bevel=0.0005)
box('BaseNamePlate', (0.05, 0.002, 0.010), (0, -0.0856, 0.007), mat=alu, bevel=0.0006)
# shoulder cheeks
for s in (-1, 1):
    box('Cheek', (0.060, 0.014, 0.089), (-0.005, 0.043 * s, 0.1455), mat=alu, bevel=0.006)
    bolt('CheekBoltA', (-0.022, 0.0505 * s, 0.122), (0, s, 0), 0.0028, 0.0035, washer=False)
    bolt('CheekBoltB', (0.014, 0.0505 * s, 0.122), (0, s, 0), 0.0028, 0.0035, washer=False)
    bolt('CheekBoltC', (-0.022, 0.043 * s, 0.103), (0, 0, 1), 0.0025, 0.003, washer=False)
    box('StopTab', (0.014, 0.010, 0.018), (-0.032, 0.030 * s, 0.148), mat=alu_dark, bevel=0.002)
# J2 shaft / gearbox / motor
cyl('J2_Pin', 0.0105, 0.19, (0, 0, 0.165), axis=(0, 1, 0), mat=steel)
for s in (-1, 1):
    cyl('J2_Thrust', 0.0135, 0.003, (0, 0.0305 * s, 0.165), axis=(0, 1, 0), mat=steel)
cyl('J2_GearFlange', 0.034, 0.003, (0, 0.0515, 0.165), axis=(0, 1, 0), mat=alu_dark)
cyl('J2_Gearbox', 0.032, 0.024, (0, 0.0645, 0.165), axis=(0, 1, 0), mat=alu, bevel=0.004)
cyl('J2_GearCap', 0.020, 0.007, (0, 0.080, 0.165), axis=(0, 1, 0), mat=alu_dark, bevel=0.002)
cyl('J2_GearCollar', 0.013, 0.006, (0, 0.0855, 0.165), axis=(0, 1, 0), mat=steel)
bolt('J2_GearHex', (0, 0.0885, 0.165), (0, 1, 0), 0.0045, 0.004, washer=False)
bolt_ring('J2_GearBolt', (0, 0.077, 0.165), (0, 1, 0), 0.024, 4, 0.0022, 0.003)
cyl('J2_MotorFlange', 0.028, 0.005, (0, -0.053, 0.165), axis=(0, 1, 0), mat=alu_dark)
cyl('J2_Motor', 0.024, 0.048, (0, -0.077, 0.165), axis=(0, 1, 0), mat=alu, bevel=0.004)
cyl('J2_MotorCap', 0.0245, 0.006, (0, -0.104, 0.165), axis=(0, 1, 0), mat=alu_dark, bevel=0.002)
cyl('J2_MotorCollar', 0.011, 0.005, (0, -0.1085, 0.165), axis=(0, 1, 0), mat=steel)
torus('J2_AccentA', 0.0248, 0.0014, (0, -0.100, 0.165), axis=(0, 1, 0), mat=accent)

# ============================================================
# LINK 1 : hubs, fork, optimized strut
# ============================================================
cyl('L1_RootHub', 0.026, 0.062, S + u1 * 0.008, axis=u1, mat=alu, bevel=0.004)
cyl('L1_RootCollar', 0.029, 0.005, S + u1 * 0.040, axis=u1, mat=alu_dark)
bolt_ring('L1_RootBolt', S + u1 * 0.0435, u1, 0.021, 4, 0.002, 0.0028)
cyl('L1_EndCollar', 0.020, 0.050, E - u1 * 0.028, axis=u1, mat=alu, bevel=0.003)
bolt_ring('L1_EndBolt', E - u1 * 0.052, -u1, 0.014, 4, 0.0018, 0.0025)
for s in (-1, 1):
    box('L1_Fork', (0.052, 0.010, 0.046), E - u1 * 0.008 + v * (0.030 * s),
        frame=(u1, v, w1), mat=alu, bevel=0.005)
s1 = optimized_strut('Strut_Upper', S + u1 * 0.036, E - u1 * 0.040,
                     0.080, 0.056, 0.0115, 0.0085, -0.012, 12, titanium)
membrane('Memb_U1', s1, 3, memb)
membrane('Memb_U2', s1, 8, memb)

# ============================================================
# ELBOW JOINT (J3)
# ============================================================
cyl('J3_Pin', 0.007, 0.10, E, axis=(0, 1, 0), mat=steel)
for s in (-1, 1):
    cyl('J3_Thrust', 0.0105, 0.0025, E + v * (0.0215 * s), axis=(0, 1, 0), mat=steel)
cyl('J3_GearFlange', 0.024, 0.004, E + v * 0.0365, axis=(0, 1, 0), mat=alu_dark)
cyl('J3_Gearbox', 0.022, 0.020, E + v * 0.049, axis=(0, 1, 0), mat=alu, bevel=0.003)
cyl('J3_GearCap', 0.014, 0.006, E + v * 0.062, axis=(0, 1, 0), mat=alu_dark)
cyl('J3_GearCollar', 0.009, 0.005, E + v * 0.067, axis=(0, 1, 0), mat=steel)
bolt('J3_GearHex', E + v * 0.070, (0, 1, 0), 0.0035, 0.0035, washer=False)
bolt_ring('J3_GearBolt', E + v * 0.0595, (0, 1, 0), 0.016, 4, 0.0018, 0.0025)
cyl('J3_MotorFlange', 0.021, 0.004, E - v * 0.0365, axis=(0, 1, 0), mat=alu_dark)
cyl('J3_Motor', 0.019, 0.028, E - v * 0.052, axis=(0, 1, 0), mat=alu, bevel=0.003)
cyl('J3_MotorCap', 0.0195, 0.005, E - v * 0.0685, axis=(0, 1, 0), mat=alu_dark)
cyl('J3_MotorCollar', 0.009, 0.004, E - v * 0.0725, axis=(0, 1, 0), mat=steel)
led('J3_LED', E + Vector((0, -0.0718, 0.006)), (0, -1, 0), leda, r=0.0018)
torus('J3_Accent', 0.0198, 0.0012, E - v * 0.065, axis=(0, 1, 0), mat=accent)

# ============================================================
# LINK 2 : eye, optimized strut, end fork
# ============================================================
box('L2_RootEye', (0.056, 0.036, 0.040), E + u2 * 0.012, frame=(u2, v, w2), mat=alu, bevel=0.012)
cyl('L2_RootCollar', 0.0195, 0.006, E + u2 * 0.041, axis=u2, mat=alu_dark)
bolt_ring('L2_RootBolt', E + u2 * 0.0445, u2, 0.0135, 4, 0.0018, 0.0025)
cyl('L2_EndCollar', 0.015, 0.050, W - u2 * 0.028, axis=u2, mat=alu, bevel=0.003)
bolt_ring('L2_EndBolt', W - u2 * 0.052, -u2, 0.010, 4, 0.0015, 0.0022)
for s in (-1, 1):
    box('L2_Fork', (0.046, 0.008, 0.034), W - u2 * 0.006 + v * (0.022 * s),
        frame=(u2, v, w2), mat=alu, bevel=0.004)
s2 = optimized_strut('Strut_Fore', E + u2 * 0.038, W - u2 * 0.034,
                     0.052, 0.034, 0.0080, 0.0058, -0.010, 11, titanium)
membrane('Memb_F1', s2, 4, memb)

# ============================================================
# WRIST (J4 pitch + J5 roll) AND PARALLEL GRIPPER
# ============================================================
cyl('J4_Pin', 0.005, 0.072, W, axis=(0, 1, 0), mat=steel)
for s in (-1, 1):
    cyl('J4_Thrust', 0.0085, 0.0015, W + v * (0.0175 * s), axis=(0, 1, 0), mat=steel)
cyl('WristBlock', 0.016, 0.034, W, axis=(0, 1, 0), mat=alu, bevel=0.003)
cyl('J4_EncFlange', 0.0145, 0.003, W + v * 0.0275, axis=(0, 1, 0), mat=alu_dark)
cyl('J4_Encoder', 0.0135, 0.010, W + v * 0.033, axis=(0, 1, 0), mat=alu, bevel=0.002)
cyl('J4_EncCap', 0.010, 0.005, W + v * 0.0405, axis=(0, 1, 0), mat=alu_dark)
cyl('J4_EncCollar', 0.006, 0.004, W + v * 0.044, axis=(0, 1, 0), mat=steel)
cyl('J4_MotorFlange', 0.014, 0.003, W - v * 0.0275, axis=(0, 1, 0), mat=alu_dark)
cyl('J4_Motor', 0.0125, 0.028, W - v * 0.0425, axis=(0, 1, 0), mat=alu, bevel=0.002)
cyl('J4_MotorCap', 0.013, 0.005, W - v * 0.059, axis=(0, 1, 0), mat=alu_dark)
led('J4_LED', W + Vector((0, -0.0622, 0.004)), (0, -1, 0), ledg, r=0.0016)
# J5 roll actuator + tool flange
cyl('J5_Actuator', 0.0145, 0.034, W + u3 * 0.019, axis=u3, mat=alu, bevel=0.003)
torus('J5_Accent', 0.0148, 0.0016, W + u3 * 0.037, axis=u3, mat=accent)
cyl('ToolFlange', 0.019, 0.006, W + u3 * 0.041, axis=u3, mat=alu_dark, bevel=0.0015)
bolt_ring('ToolBolt', W + u3 * 0.0445, u3, 0.0135, 4, 0.002, 0.0028)
# gripper body
box('GripperBody', (0.036, 0.040, 0.024), W + u3 * 0.065, frame=(u3, v, w3), mat=alu_dark, bevel=0.005)
box('GripperFrontPlate', (0.004, 0.036, 0.020), W + u3 * 0.0845, frame=(u3, v, w3), mat=alu, bevel=0.001)
bolt_ring('GripPlateBolt', W + u3 * 0.0868, u3, 0.011, 4, 0.0016, 0.0022)
led('GripLED', W + u3 * 0.060 - v * 0.0206, (0, -1, 0), leda, r=0.0015)
# servo on top
cyl('GripServoFlange', 0.010, 0.003, W + u3 * 0.058 + w3 * 0.0125, axis=w3, mat=alu_dark)
cyl('GripServo', 0.0085, 0.018, W + u3 * 0.058 + w3 * 0.021, axis=w3, mat=alu, bevel=0.002)
cyl('GripServoCap', 0.0088, 0.004, W + u3 * 0.058 + w3 * 0.031, axis=w3, mat=alu_dark)
# rails + end plates
for s in (-1, 1):
    cyl('Rail', 0.0028, 0.045, W + u3 * 0.092 + w3 * (0.0065 * s), axis=(0, 1, 0), mat=steel, verts=20)
    box('RailEndPlate', (0.026, 0.004, 0.018), W + u3 * 0.092 + v * (0.0225 * s),
        frame=(u3, v, w3), mat=alu, bevel=0.002)
    # carriage, pivot, finger, pad
    box('Carriage', (0.014, 0.009, 0.016), W + u3 * 0.095 + v * (0.017 * s),
        frame=(u3, v, w3), mat=alu, bevel=0.002)
    cyl('FingerPivot', 0.0032, 0.012, W + u3 * 0.092 + v * (0.017 * s), axis=(0, 1, 0), mat=steel, verts=20)
    cyl('PivotCap', 0.0048, 0.0015, W + u3 * 0.092 + v * (0.0232 * s), axis=(0, 1, 0), mat=alu_dark, verts=20)
    box('Finger', (0.046, 0.0045, 0.012), W + u3 * 0.107 + v * (0.017 * s),
        frame=(u3, v, w3), mat=alu, bevel=0.0015)
    box('Pad', (0.012, 0.0030, 0.010), W + u3 * 0.124 + v * (0.01525 * s),
        frame=(u3, v, w3), mat=padm, bevel=0.001)
    for ds, dw in ((0.120, 0.003), (0.128, -0.003)):
        bolt('PadBolt', W + u3 * ds + w3 * dw + v * (0.0193 * s), (0, s, 0), 0.0016, 0.002, washer=False)
    rod('LinkRod', W + u3 * 0.058 + w3 * 0.0305,
        W + u3 * 0.100 + v * (0.017 * s) + w3 * 0.0075, 0.0016)

# ============================================================
# CONTROLLED CABLE ROUTING (slim black lines + clips)
# ============================================================
cyl('GrommetA', 0.005, 0.006, (-0.022, -0.033, 0.100), mat=alu_dark, verts=20)
cable('Cable_J2Motor', [(0, -0.107, 0.156), (-0.005, -0.094, 0.126),
                        (-0.014, -0.058, 0.106), (-0.022, -0.034, 0.102)], 0.0020)
cyl('GrommetB', 0.005, 0.006, (0.012, -0.026, 0.100), mat=alu_dark, verts=20)
pts = [(0.012, -0.026, 0.104)]
pts += scpts(s1, 0.072, [0.10, 0.28, 0.52, 0.74], -0.026)
pts += [s1['center'](0.92) - s1['w'] * 0.060 + s1['v'] * (-0.05),
        Vector((E.x, -0.069, E.z + 0.004))]
cable('Cable_Link1', pts, 0.0022)
for t in (0.28, 0.52, 0.74):
    cable_clip('Clip1', s1, t, 0.072)
pts = [Vector((E.x + 0.003, -0.069, E.z + 0.008))]
pts += scpts(s2, 0.050, [0.15, 0.40, 0.65, 0.88], -0.024)
pts += [Vector((W.x, -0.056, W.z + 0.004))]
cable('Cable_Link2', pts, 0.0020)
for t in (0.40, 0.70):
    cable_clip('Clip2', s2, t, 0.050)
cable('Cable_Servo', [W - v * 0.017 + w2 * 0.006,
                      W + u3 * 0.030 - v * 0.020 + w3 * 0.006,
                      W + u3 * 0.058 + w3 * 0.020 - v * 0.009], 0.0016)

# ============================================================
# TARGET OBJECT + SMALL DESK PARTS
# ============================================================
OX = Vector((obj_x, 0, 0))
cyl('Obj_Puck', 0.020, 0.006, OX + Vector((0, 0, 0.003)), mat=alu, bevel=0.0015)
torus('Obj_Ring', 0.0175, 0.0012, OX + Vector((0, 0, 0.006)), mat=accent)
cyl('Obj_Neck', 0.0115, 0.014, OX + Vector((0, 0, 0.013)), mat=alu, bevel=0.002)
cyl('Obj_Head', 0.013, 0.016, OX + Vector((0, 0, 0.028)), mat=alu, bevel=0.004)
torus('Obj_Ring2', 0.0128, 0.0014, OX + Vector((0, 0, 0.024)), mat=accent)
cyl('Obj_Socket', 0.004, 0.0015, OX + Vector((0, 0, 0.0362)), mat=alu_dark, verts=6, smooth=False)
# small bearing + washer nearby
BX = Vector((0.395, 0.115, 0))
torus('BearingOuter', 0.0095, 0.0026, BX + Vector((0, 0, 0.0032)), mat=steel)
torus('BearingInner', 0.0050, 0.0022, BX + Vector((0, 0, 0.0030)), mat=alu_dark)
for k in range(6):
    a = 2 * pi * k / 6
    cyl('Ball', 0.0021, 0.0042, BX + Vector((0.0074 * cos(a), 0.0074 * sin(a), 0.0031)),
        mat=steel, verts=16)
torus('Washer', 0.0045, 0.0016, (0.435, 0.075, 0.0016), mat=steel)

# ============================================================
# CONTROLLER BOX + DESK CABLE
# ============================================================
box('Controller', (0.10, 0.070, 0.032), (-0.20, -0.13, 0.016), mat=boxm, bevel=0.006)
box('ControllerLid', (0.090, 0.060, 0.003), (-0.20, -0.13, 0.0330), mat=alu_dark, bevel=0.001)
led('CtrlLED', (-0.175, -0.145, 0.0350), (0, 0, 1), ledg, r=0.0018)
cyl('CtrlGrommet', 0.0045, 0.005, (-0.149, -0.115, 0.014), axis=(1, 0, 0), mat=alu_dark, verts=20)
cyl('BaseGrommet', 0.005, 0.006, (0, -0.0605, 0.022), axis=(0, 1, 0), mat=alu_dark, verts=20)
cable('Cable_Desk', [(-0.146, -0.115, 0.014), (-0.10, -0.105, 0.005), (-0.05, -0.085, 0.004),
                     (-0.015, -0.070, 0.005), (0.0, -0.062, 0.018)], 0.0022)

# ============================================================
# DESK, LEGS, STUDIO COVE
# ============================================================
box('Desk', (1.6, 0.95, 0.045), (0.18, 0.02, -0.0225), mat=deskm, bevel=0.012)
for lx in (-0.48, 0.84):
    box('DeskLeg', (0.05, 0.85, 0.755), (lx, 0.02, -0.4225), mat=alu_dark, bevel=0.008)
prof = [(-3.0, -0.80), (1.0, -0.80)]
for k in range(1, 10):
    a = pi / 2 * k / 9
    prof.append((1.0 + 0.9 * sin(a), -0.80 + 0.9 * (1 - cos(a))))
prof.append((1.9, 3.0))
cverts = [(x, y, z) for (y, z) in prof for x in (-3.5, 3.5)]
cfaces = [(2 * i, 2 * i + 1, 2 * i + 3, 2 * i + 2) for i in range(len(prof) - 1)]
cme = bpy.data.meshes.new('Cove')
cme.from_pydata(cverts, [], cfaces)
cove = bpy.data.objects.new('StudioCove', cme)
bpy.context.scene.collection.objects.link(cove)
for p in cme.polygons: p.use_smooth = True
cme.materials.append(floorm)
sol = cove.modifiers.new('Solid', 'SOLIDIFY'); sol.thickness = 0.05; sol.offset = 0.0

# ============================================================
# LIGHTING / CAMERA / WORLD
# ============================================================
def area_light(name, loc, energy, size, target, color=(1, 1, 1)):
    ld = bpy.data.lights.new(name, 'AREA'); ld.energy = energy; ld.color = color
    ld.shape = 'DISK'; ld.size = size
    ob = bpy.data.objects.new(name, ld)
    bpy.context.scene.collection.objects.link(ob)
    ob.location = loc
    look_at(ob, target)
    return ob

area_light('Key', (0.5, -1.0, 1.6), 800, 0.9, (0.25, 0, 0.2), (1.0, 0.96, 0.90))
area_light('Fill', (-1.1, -0.6, 0.7), 280, 0.8, (0.2, 0, 0.2), (0.85, 0.90, 1.0))
area_light('Rim', (0.7, 1.1, 1.4), 650, 0.6, (0.3, 0, 0.25), (1.0, 0.95, 0.90))
area_light('Top', (0.1, 0.3, 2.3), 450, 1.0, (0.2, 0, 0), (1.0, 1.0, 1.0))

world = bpy.data.worlds.new('Studio')
scene.world = world; world.use_nodes = True
bg = world.node_tree.nodes['Background']
bg.inputs['Color'].default_value = (0.62, 0.63, 0.67, 1)
bg.inputs['Strength'].default_value = 0.35

cd = bpy.data.cameras.new('PresentationCam')
cd.lens = 60; cd.sensor_width = 36
cam = bpy.data.objects.new('PresentationCam', cd)
bpy.context.scene.collection.objects.link(cam)
cam.location = (1.00, -1.30, 0.58)
look_at(cam, (0.21, 0.0, 0.175))
cd.dof.use_dof = True
cd.dof.aperture_fstop = 7.0
focus = bpy.data.objects.new('FocusPoint', None)
bpy.context.scene.collection.objects.link(focus)
focus.location = (0.37, 0, 0.15)
cd.dof.focus_object = focus
scene.camera = cam

bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.update()

# ============================================================
# SAVE
# ============================================================
try:
    bpy.ops.wm.save_as_mainfile(filepath=bpy.path.abspath('//robotic_arm_studio.blend'))
except Exception as e:
    print('Save skipped:', e)
print('ROBOT ARM SCENE BUILD COMPLETE')
```