
import bpy, math
from mathutils import Vector

# CARE-ED standalone animated fidget toy scene.
# Run this file in Blender 3.6+ / 4.x: Scripting > Open > Run Script.
# Components are separate objects. Motion is keyframed kinematic animation,
# not a rigid-body physics validation.

bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

scene = bpy.context.scene
scene.frame_start = 1
scene.frame_end = 120
scene.render.fps = 30
scene.render.resolution_x = 1200
scene.render.resolution_y = 900

def mat(name, color, metallic=0.0, roughness=0.34):
    m = bpy.data.materials.new(name)
    m.diffuse_color = (*color, 1)
    m.use_nodes = True
    bs = m.node_tree.nodes.get("Principled BSDF")
    if bs:
        bs.inputs["Base Color"].default_value = (*color, 1)
        bs.inputs["Metallic"].default_value = metallic
        bs.inputs["Roughness"].default_value = roughness
    return m

TEAL=mat("Care-Ed Teal",(0.025,0.48,0.45),0.08)
PURPLE=mat("Care-Ed Purple",(0.36,0.20,0.68),0.04)
CORAL=mat("Care-Ed Coral",(0.92,0.22,0.18))
YELLOW=mat("Care-Ed Yellow",(0.98,0.65,0.12),0.08)
DARK=mat("Graphite",(0.08,0.10,0.13),0.15)
WHITE=mat("Warm White",(0.9,0.93,0.9))

def finish(o, material, bevel_width=0.035):
    if material: o.data.materials.append(material)
    if o.type == 'MESH':
        for p in o.data.polygons: p.use_smooth=True
    if bevel_width and o.type == 'MESH':
        b=o.modifiers.new("Rounded manufactured edges",'BEVEL')
        b.width=bevel_width; b.segments=3
        o.modifiers.new("Weighted normals",'WEIGHTED_NORMAL')
    return o

def box(name, loc, dims, material, bevel_width=0.04):
    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)
    return finish(o,material,bevel_width)

def cyl(name, loc, radius, depth, material, rotation=(0,0,0), bevel_width=0.025, vertices=48):
    bpy.ops.mesh.primitive_cylinder_add(vertices=vertices,radius=radius,depth=depth,location=loc,rotation=rotation)
    o=bpy.context.object; o.name=name
    return finish(o,material,bevel_width)

def sphere(name, loc, scale, material):
    bpy.ops.mesh.primitive_uv_sphere_add(segments=32,ring_count=20,location=loc)
    o=bpy.context.object; o.name=name; o.scale=scale
    bpy.ops.object.transform_apply(location=False,rotation=False,scale=True)
    return finish(o,material,0)

def torus(name, loc, major, minor, material, rotation=(0,0,0)):
    bpy.ops.mesh.primitive_torus_add(major_radius=major,minor_radius=minor,location=loc,rotation=rotation)
    o=bpy.context.object; o.name=name
    return finish(o,material,0)

def parent_keep_world(child, parent):
    world=child.matrix_world.copy()
    child.parent=parent
    child.matrix_world=world

def animate_rotation(obj, axis=2, turns=1, start=1, end=120):
    """Keyframe rotation without relying on Blender-version-specific Action FCurves APIs."""
    obj.rotation_mode='XYZ'
    obj.keyframe_insert(data_path='rotation_euler',frame=start,group='Mechanism Motion')
    r=list(obj.rotation_euler)
    r[axis] += turns*2*math.pi
    obj.rotation_euler=r
    obj.keyframe_insert(data_path='rotation_euler',frame=end,group='Mechanism Motion')


def animate_linear(obj, start_loc, end_loc, start=1, mid=30, end=60):
    """Keyframe a back-and-forth translation using Blender's stable object API."""
    obj.location=start_loc
    obj.keyframe_insert(data_path='location',frame=start,group='Mechanism Motion')
    obj.location=end_loc
    obj.keyframe_insert(data_path='location',frame=mid,group='Mechanism Motion')
    obj.location=start_loc
    obj.keyframe_insert(data_path='location',frame=end,group='Mechanism Motion')


def label(body, loc, size=0.35):
    curve=bpy.data.curves.new(body,'FONT')
    curve.body=body; curve.size=size; curve.align_x='CENTER'
    o=bpy.data.objects.new(body,curve); bpy.context.collection.objects.link(o)
    o.location=loc; o.data.materials.append(WHITE)
    return o

def studio(title, center=(0,0,0), scale=5.5):
    floor=box("Studio Ground",(0,0,-0.20),(12,9,0.25),DARK,0.02)
    label(title,(0,-3.6,0.0),0.32)
    bpy.ops.object.camera_add(location=(0,-7,7))
    cam=bpy.context.object; cam.name="Studio Camera"
    direction=Vector(center)-cam.location
    cam.rotation_euler=direction.to_track_quat('-Z','Y').to_euler()
    cam.data.type='ORTHO'; cam.data.ortho_scale=scale
    scene.camera=cam
    for name,loc,power,size in [
        ("Key Light",(-4,-4,7),1300,6),
        ("Fill Light",(4,-1,5),900,5),
        ("Rim Light",(0,4,6),1000,4)]:
        bpy.ops.object.light_add(type='AREA',location=loc)
        l=bpy.context.object; l.name=name; l.data.energy=power; l.data.shape='DISK'; l.data.size=size
        l.rotation_euler=(Vector((0,0,0))-l.location).to_track_quat('-Z','Y').to_euler()
    scene.frame_set(1)


# FLEX-LINK: articulated segments pivot at hinges in a linked chain.
box("FlexLink | Display Base",(0,0,0.08),(3.7,1.2,0.16),DARK,0.08)
# Each hinge empty is located at the joint; downstream segment is parented to it.
A=box("FlexLink | Segment A",(-0.75,0,0.42),(0.95,0.34,0.16),TEAL,0.10)
pivot1=bpy.data.objects.new("FlexLink | Hinge 1",None); bpy.context.collection.objects.link(pivot1)
pivot1.location=(-0.25,0,0.42); pivot1.empty_display_type='CIRCLE'; pivot1.empty_display_size=0.18
parent_keep_world(A,pivot1)
# Segment B is positioned with its left end at pivot1; use a hinge pivot for it
B=box("FlexLink | Segment B",(0.25,0,0.42),(0.95,0.34,0.16),PURPLE,0.10)
pivot2=bpy.data.objects.new("FlexLink | Hinge 2",None); bpy.context.collection.objects.link(pivot2)
pivot2.location=(0.75,0,0.42); pivot2.empty_display_type='CIRCLE'; pivot2.empty_display_size=0.18
parent_keep_world(B,pivot2)
C=box("FlexLink | Segment C",(1.25,0,0.42),(0.95,0.34,0.16),TEAL,0.10)
# Visible hinge pins
cyl("FlexLink | Pivot Pin 1",(-0.25,0,0.53),0.11,0.12,YELLOW)
cyl("FlexLink | Pivot Pin 2",(0.75,0,0.53),0.11,0.12,YELLOW)
# Keyframe pivot rotations, not segment geometry; each part swings around its hinge.
for pivot,vals in [
    (pivot1,[(1,0),(30,28),(60,0),(90,-28),(120,0)]),
    (pivot2,[(1,0),(30,-22),(60,0),(90,22),(120,0)])]:
    for frame,deg in vals:
        pivot.rotation_euler[2]=math.radians(deg)
        pivot.keyframe_insert(data_path='rotation_euler',frame=frame,group='Mechanism Motion')
label("FLEX-LINK",(0,-1.25,0.02))
studio("CARE-ED • FLEX-LINK",(0,0,0.35),5.7)
