```python
import bpy, bmesh, math, random
from mathutils import Vector, Matrix

random.seed(42)

# ---------- CLEANUP ----------
for c in list(bpy.data.collections):
    bpy.data.collections.remove(c)
for o in list(bpy.data.objects):
    bpy.data.objects.remove(o, do_unlink=True)
for m in list(bpy.data.meshes):
    bpy.data.meshes.remove(m)
for mat in list(bpy.data.materials):
    bpy.data.materials.remove(mat)

world = bpy.data.worlds.new("World")
bpy.context.scene.world = world
world.node_tree.nodes["Background"].inputs[1].default_value = 1.0
world.node_tree.nodes["Background"].inputs[0].default_value = (0.02,0.02,0.02,1)

scene = bpy.context.scene
scene.render.engine = 'CYCLES'
scene.cycles.samples = 64
scene.view_settings.view_transform = 'AgX'

# ---------- MATERIALS ----------

def make_leather_material(name, base_col, worn_col, mud=False):
    mat = bpy.data.materials.new(name)
    mat.use_nodes = True
    nt = mat.node_tree
    nodes = nt.nodes
    links = nt.links
    for n in list(nodes):
        nodes.remove(n)

    out = nodes.new("ShaderNodeOutputMaterial")
    bsdf = nodes.new("ShaderNodeBsdfPrincipled")
    bsdf.location = (0,0)
    out.location = (400,0)
    links.new(bsdf.outputs[0], out.inputs[0])

    tex_coord = nodes.new("ShaderNodeTexCoord")
    tex_coord.location = (-1200,0)

    # grain noise
    grain = nodes.new("ShaderNodeTexNoise")
    grain.location = (-1000, 300)
    grain.inputs['Scale'].default_value = 80.0
    grain.inputs['Detail'].default_value = 8.0
    grain.inputs['Roughness'].default_value = 0.7
    links.new(tex_coord.outputs['Object'], grain.inputs['Vector'])

    grain_bump = nodes.new("ShaderNodeBump")
    grain_bump.inputs['Strength'].default_value = 0.15
    grain_bump.location = (-600, 300)
    links.new(grain.outputs['Fac'], grain_bump.inputs['Height'])

    # pore variation for roughness
    pore = nodes.new("ShaderNodeTexNoise")
    pore.location = (-1000, 0)
    pore.inputs['Scale'].default_value = 220.0
    pore.inputs['Detail'].default_value = 10.0
    links.new(tex_coord.outputs['Object'], pore.inputs['Vector'])

    # large-scale wear mask (burnish/scuff)
    wear = nodes.new("ShaderNodeTexNoise")
    wear.location = (-1000, -300)
    wear.inputs['Scale'].default_value = 6.0
    wear.inputs['Detail'].default_value = 4.0
    links.new(tex_coord.outputs['Object'], wear.inputs['Vector'])

    colorramp = nodes.new("ShaderNodeValToRGB")
    colorramp.location = (-700, -300)
    colorramp.color_ramp.elements[0].position = 0.3
    colorramp.color_ramp.elements[0].color = (*worn_col, 1)
    colorramp.color_ramp.elements[1].position = 0.7
    colorramp.color_ramp.elements[1].color = (*base_col, 1)
    links.new(wear.outputs['Fac'], colorramp.inputs['Fac'])

    links.new(colorramp.outputs['Color'], bsdf.inputs['Base Color'])
    links.new(grain_bump.outputs['Normal'], bsdf.inputs['Normal'])

    roughmap = nodes.new("ShaderNodeMapRange")
    roughmap.location = (-700, 0)
    roughmap.inputs['To Min'].default_value = 0.25
    roughmap.inputs['To Max'].default_value = 0.75
    links.new(pore.outputs['Fac'], roughmap.inputs['Value'])
    links.new(roughmap.outputs['Result'], bsdf.inputs['Roughness'])

    bsdf.inputs['Specular IOR Level'].default_value = 0.5
    bsdf.inputs['IOR'].default_value = 1.45

    if mud:
        mud_noise = nodes.new("ShaderNodeTexNoise")
        mud_noise.location = (-1000, -600)
        mud_noise.inputs['Scale'].default_value = 30.0
        mudramp = nodes.new("ShaderNodeValToRGB")
        mudramp.location = (-700, -600)
        mudramp.color_ramp.elements[0].position = 0.55
        mudramp.color_ramp.elements[0].color = (0.08,0.06,0.04,1)
        mudramp.color_ramp.elements[1].position = 0.65
        mudramp.color_ramp.elements[1].color = (*worn_col,1)
        links.new(mud_noise.outputs['Fac'], mudramp.inputs['Fac'])
        links.new(tex_coord.outputs['Object'], mud_noise.inputs['Vector'])
        mix = nodes.new("ShaderNodeMixRGB")
        mix.location = (-350,-150)
        mix.inputs['Fac'].default_value = 0.0
        # use vertex color 'mud_mask' if present to drive mix
        vc = nodes.new("ShaderNodeVertexColor")
        vc.layer_name = "mud_mask"
        vc.location = (-700, -900)
        links.new(vc.outputs['Color'], mix.inputs['Fac'])
        links.new(colorramp.outputs['Color'], mix.inputs['Color1'])
        links.new(mudramp.outputs['Color'], mix.inputs['Color2'])
        links.new(mix.outputs['Color'], bsdf.inputs['Base Color'])

    return mat

leather_upper = make_leather_material("Leather_Upper", (0.28,0.15,0.08), (0.55,0.35,0.18))
leather_sole_mat = make_leather_material("Leather_Sole", (0.05,0.04,0.035), (0.12,0.09,0.07), mud=True)
leather_welt = make_leather_material("Leather_Welt", (0.22,0.12,0.06), (0.4,0.25,0.13))

def make_metal_material(name):
    mat = bpy.data.materials.new(name)
    mat.use_nodes = True
    nt = mat.node_tree
    bsdf = nt.nodes["Principled BSDF"]
    bsdf.inputs['Metallic'].default_value = 1.0
    noise = nt.nodes.new("ShaderNodeTexNoise")
    noise.inputs['Scale'].default_value = 40.0
    ramp = nt.nodes.new("ShaderNodeValToRGB")
    ramp.color_ramp.elements[0].position = 0.4
    ramp.color_ramp.elements[0].color = (0.3,0.28,0.25,1)
    ramp.color_ramp.elements[1].position = 0.8
    ramp.color_ramp.elements[1].color = (0.8,0.78,0.72,1)
    nt.links.new(noise.outputs['Fac'], ramp.inputs['Fac'])
    roughmap = nt.nodes.new("ShaderNodeMapRange")
    roughmap.inputs['To Min'].default_value = 0.15
    roughmap.inputs['To Max'].default_value = 0.55
    nt.links.new(noise.outputs['Fac'], roughmap.inputs['Value'])
    nt.links.new(roughmap.outputs['Result'], bsdf.inputs['Roughness'])
    nt.links.new(ramp.outputs['Color'], bsdf.inputs['Base Color'])
    return mat

metal_mat = make_metal_material("Metal_Eyelet")

def make_lace_material():
    mat = bpy.data.materials.new("Lace")
    mat.use_nodes = True
    bsdf = mat.node_tree.nodes["Principled BSDF"]
    bsdf.inputs['Base Color'].default_value = (0.05,0.04,0.03,1)
    bsdf.inputs['Roughness'].default_value = 0.7
    return mat

lace_mat = make_lace_material()

def make_backdrop_material():
    mat = bpy.data.materials.new("Backdrop")
    mat.use_nodes = True
    bsdf = mat.node_tree.nodes["Principled BSDF"]
    bsdf.inputs['Base Color'].default_value = (0.82,0.82,0.83,1)
    bsdf.inputs['Roughness'].default_value = 0.9
    return mat

backdrop_mat = make_backdrop_material()

# ---------- BOOT MESH (upper) ----------
# Build boot upper via lofted profile curves along a path, then sculpt asymmetry with proportional displacement.

def build_boot_upper():
    bm = bmesh.new()

    # profile stations along foot length (x) with y (width offsets) and z heights, per station a ring of verts
    # We'll construct a lofted tube shape approximating a boot: toe -> vamp -> ankle collar
    stations = []
    # x from toe(0.0) to back of heel(-length) - use x positive forward, boot length ~0.28
    xs = [0.14, 0.11, 0.07, 0.02, -0.03, -0.08, -0.12, -0.12]
    widths = [0.045,0.05,0.052,0.05,0.048,0.05,0.052,0.052]
    heights_top = [0.02,0.04,0.075,0.10,0.13,0.16,0.18,0.19]
    heights_bottom = [-0.02]*8
    n_ring = 16

    rings = []
    for i,(x,w,ht,hb) in enumerate(zip(xs,widths,heights_top,heights_bottom)):
        ring = []
        cz = (ht+hb)/2.0
        rz = (ht-hb)/2.0
        for j in range(n_ring):
            t = j/n_ring*2*math.pi
            # squashed oval cross-section, flatter at bottom
            yy = math.sin(t)*w
            zz = cz + math.cos(t)*rz
            if math.cos(t) < 0:
                zz = cz + math.cos(t)*rz*0.6  # flatten bottom
            # asymmetry: bulge outward slightly more on one side (left boot -> +y outer)
            yy *= (1.0 + 0.08*math.sin(t))
            v = bm.verts.new((x, yy, zz))
            ring.append(v)
        rings.append(ring)

    # bridge rings with faces
    for i in range(len(rings)-1):
        r0, r1 = rings[i], rings[i+1]
        for j in range(n_ring):
            j2 = (j+1)%n_ring
            bm.faces.new((r0[j], r0[j2], r1[j2], r1[j]))

    # cap back (heel) end
    back_ring = rings[-1]
    try:
        bm.faces.new(back_ring)
    except Exception:
        pass

    # cap front... leave open at toe for now, will build toe cap separately, so close last-first also open for toe merge later
    front_ring = rings[0]

    bm.normal_update()
    me = bpy.data.meshes.new("BootUpper")
    bm.to_mesh(me)
    bm.free()
    obj = bpy.data.objects.new("BootUpper", me)
    bpy.context.collection.objects.link(obj)
    return obj, front_ring

# Simpler and more robust approach: build boot via a single deformed mesh using primitive combination
# Use a cube -> subsurf -> sculpt-like displacement with noise textures added as a modifier for creases.

def add_boot_shape():
    bpy.ops.mesh.primitive_cube_add(size=1)
    obj = bpy.context.active_object
    obj.name = "Boot_Upper"
    me = obj.data
    bm = bmesh.new()
    bm.from_mesh(me)
    bmesh.ops.subdivide_edges(bm, edges=bm.edges[:], cuts=6, use_grid_fill=True)
    bm.to_mesh(me)
    bm.free()

    obj.scale = (0.15, 0.28, 0.09)
    bpy.ops.object.transform_apply(scale=True)
    obj.location = (0,0,0.09)

    # shape into boot form using vertex manipulation
    me = obj.data
    for v in me.vertices:
        x,y,z = v.co
        # y is length axis (toe to heel), x is width, z height
        t = (y + 0.28) / 0.56  # 0 heel .. 1 toe approx (since obj scaled y .28)
        t = max(0.0, min(1.0, t))
        # taper toe rounder, ankle wider/taller at heel end
        # height profile: low at toe, rises to ankle collar near heel(back)
        height_scale = 1.0
        if t > 0.75:  # toe area
            width_scale = 0.9 - (t-0.75)*0.6
            z_off = -0.02
        elif t < 0.25:  # heel/ankle collar area (back)
            width_scale = 1.0
            z_off = 0.11*(0.25-t)/0.25
        else:
            width_scale = 1.0
            z_off = 0.02
        v.co.x *= width_scale
        v.co.z += z_off
        # add rounded toe bulge
        if t > 0.85:
            bulge = (t-0.85)/0.15
            v.co.z += bulge*0.02
    obj.data.update()
    return obj

boot_upper = add_boot_shape()

# subsurf + shrinkwrap-esque smoothing then add crease displacement using noise + manual sculpt via shape keys
mod = boot_upper.modifiers.new("Subsurf", 'SUBSURF')
mod.levels = 2
mod.render_levels = 2

bpy.context.view_layer.objects.active = boot_upper
bpy.ops.object.modifier_apply(modifier="Subsurf")

# Add displacement texture for leather creases at flex zone (above toe)
disp_tex = bpy.data.textures.new("CreaseTex", type='DISTORTED_NOISE')
disp_tex.noise_scale = 0.4

disp_mod = boot_upper.modifiers.new("CreaseDisp", 'DISPLACE')
disp_mod.texture = disp_tex
disp_mod.strength = 0.01
disp_mod.mid_level = 0.5

# vertex group limited to flex zone (over the toe, front-upper area)
vg = boot_upper.vertex_groups.new(name="FlexZone")
me = boot_upper.data
for v in me.vertices:
    x,y,z = v.co
    t = (y + 0.28)/0.56
    t = max(0,min(1,t))
    w = 0.0
    if 0.55 < t < 0.85 and z > 0.02:
        w = 1.0 - abs(t-0.7)/0.15
        w = max(0,w)
    vg.add([v.index], w, 'REPLACE')
disp_mod.vertex_group = "FlexZone"

# Asymmetric slump: push collar outward on one side + heel roll
me = boot_upper.data
for v in me.vertices:
    x,y,z = v.co
    t = (y + 0.28)/0.56
    t = max(0,min(1,t))
    if t < 0.2 and z > 0.12:  # collar area
        v.co.x += 0.012 * (0.2-t)/0.2 * (1 if x>0 else 1.3)
        v.co.y -= 0.005*(0.2-t)/0.2
    # outer heel roll at bottom
    if t < 0.15 and z < 0.0 and x < 0:
        v.co.z -= 0.006
me.update()

boot_upper.data.materials.append(leather_upper)
bpy.ops.object.shade_smooth()

# ---------- TONGUE ----------
bpy.ops.mesh.primitive_cube_add(size=1)
tongue = bpy.context.active_object
tongue.name = "Tongue"
tongue.scale = (0.05, 0.07, 0.02)
bpy.ops.object.transform_apply(scale=True)
tongue.location = (0, 0.02, 0.155)
tongue.rotation_euler = (math.radians(8),0,0)
mod = tongue.modifiers.new("Sub", 'SUBSURF'); mod.levels=2
bpy.context.view_layer.objects.active = tongue
bpy.ops.object.modifier_apply(modifier="Sub")
tongue.data.materials.append(leather_upper)
bpy.ops.object.shade_smooth()

# ---------- SOLE ----------
bpy.ops.mesh.primitive_cube_add(size=1)
sole = bpy.context.active_object
sole.name = "Sole"
sole.scale = (0.155, 0.30, 0.035)
bpy.ops.object.transform_apply(scale=True)
sole.location = (0,0,0.017)
me = sole.data
for v in me.vertices:
    if v.co.z > 0:
        v.co.z *= 0.4
me.update()
mod = sole.modifiers.new("Bevel",'BEVEL')
mod.width=0.01; mod.segments=3
bpy.context.view_layer.objects.active = sole
bpy.ops.object.modifier_apply(modifier="Bevel")

# heel block
bpy.ops.mesh.primitive_cube_add(size=1)
heel = bpy.context.active_object
heel.name = "HeelBlock"
heel.scale = (0.14, 0.09, 0.025)
bpy.ops.object.transform_apply(scale=True)
heel.location = (-0.005, -0.20, -0.006)
mod = heel.modifiers.new("Bevel",'BEVEL'); mod.width=0.008; mod.segments=2
bpy.context.view_layer.objects.active = heel
bpy.ops.object.modifier_apply(modifier="Bevel")
# subtle roll: tilt heel slightly for worn walk
heel.rotation_euler = (math.radians(-2), math.radians(1.5), 0)

sole.data.materials.append(leather_sole_mat)
heel.data.materials.append(leather_sole_mat)

# tread pattern via boolean grooves
def add_tread(base_obj, count=10):
    grooves = []
    for i in range(count):
        bpy.ops.mesh.primitive_cube_add(size=1)
        g = bpy.context.active_object
        g.scale = (0.16, 0.006, 0.02)
        bpy.ops.object.transform_apply(scale=True)
        yy = -0.27 + i*0.058
        g.location = (0, yy, -0.005)
        grooves.append(g)
    bpy.ops.object.select_all(action='DESELECT')
    for g in grooves:
        g.select_set(True)
    bpy.context.view_layer.objects.active = grooves[0]
    bpy.ops.object.join()
    groove_union = bpy.context.active_object
    groove_union.name = "TreadGrooves"

    bmod = base_obj.modifiers.new("TreadBool", 'BOOLEAN')
    bmod.operation = 'DIFFERENCE'
    bmod.object = groove_union
    bpy.context.view_layer.objects.active = base_obj
    bpy.ops.object.modifier_apply(modifier="TreadBool")
    bpy.data.objects.remove(groove_union, do_unlink=True)

add_tread(sole, count=9)
bpy.ops.object.shade_flat()
bpy.context.view_layer.objects.active = sole
bpy.ops.object.shade_smooth()

# mud vertex color on sole (edges/welt)
vc = sole.data.vertex_colors.new(name="mud_mask")
for poly in sole.data.polygons:
    for li in poly.loop_indices:
        vidx = sole.data.loops[li].vertex_index
        co = sole.data.vertices[vidx].co
        val = 1.0 if abs(co.z) > 0.012 or abs(co.x) > 0.14 else 0.0
        vc.data[li].color = (val,val,val,1)

# ---------- WELT LINE (stitch relief) ----------
bpy.ops.mesh.primitive_torus_add(major_radius=0.15, minor_radius=0.004, major_segments=48, minor_segments=6)
welt = bpy.context.active_object
welt.name = "WeltLine"
welt.scale = (1.0, 1.9, 0.5)
welt.location = (0, -0.02, 0.005)
bpy.ops.object.transform_apply(scale=True)
welt.data.materials.append(leather_welt)

# stitch dashes around welt using array of small cubes following curve approx
bpy.ops.curve.primitive_bezier_circle_add(radius=0.15)
welt_curve = bpy.context.active_object
welt_curve.scale = (1.0,1.9,1.0)
welt_curve.location = (0,-0.02, 0.006)
bpy.ops.object.transform_apply(scale=True)

bpy.ops.mesh.primitive_cube_add(size=1)
stitch = bpy.context.active_object
stitch.scale = (0.006,0.002,0.002)
bpy.ops.object.transform_apply(scale=True)
stitch.name = "StitchUnit"
arr = stitch.modifiers.new("Arr",'ARRAY')
arr.fit_type='FIT_CURVE'
arr.curve = welt_curve
arr.count = 90
arr.relative_offset_displace[0] = 2.2
curvem = stitch.modifiers.new("Curve",'CURVE')
curvem.object = welt_curve
stitch.data.materials.append(leather_welt)
bpy.context.view_layer.objects.active = stitch
bpy.ops.object.convert(target='MESH')

# ---------- EYELETS + LACES ----------
eyelet_objs = []
n_pairs = 7
for side in (-1,1):
    for i in range(n_pairs):
        bpy.ops.mesh.primitive_torus_add(major_radius=0.006, minor_radius=0.0015, major_segments=12, minor_segments=8)
        eye = bpy.context.active_object
        yy = 0.09 - i*0.028
        xx = side*0.028
        zz = 0.10 + i*0.012
        eye.location = (xx, yy, zz)
        eye.rotation_euler = (math.radians(90), 0, math.radians(10*side))
        eye.data.materials.append(metal_mat)
        eye.name = f"Eyelet_{side}_{i}"
        eyelet_objs.append(eye)

# lace curve: criss-cross zigzag, loose at top two rows, frayed end
lace_points = []
for i in range(n_pairs):
    side = 1 if i%2==0 else -1
    yy = 0.09 - i*0.028
    zz = 0.10 + i*0.012
    xx = side*0.03
    lace_points.append((xx,yy,zz))
    xx2 = -side*0.03
    lace_points.append((xx2,yy,zz+0.003))

# loose ends hanging at top (last two rows) with frayed tips
tail1 = (0.03, 0.09-6*0.028, 0.10+6*0.012)
tail2 = (0.05, 0.09-6*0.028-0.06, 0.03)  # hangs down loosely, frayed
lace_points.append(tail2)

curve_data = bpy.data.curves.new('LaceCurve', type='CURVE')
curve_data.dimensions = '3D'
curve_data.bevel_depth = 0.0025
curve_data.bevel_resolution = 4
polyline = curve_data.splines.new('BEZIER')
polyline.bezier_points.add(len(lace_points)-1)
for i,p in enumerate(lace_points):
    bp = polyline.bezier_points[i]
    bp.co = p
    bp.handle_left_type='AUTO'
    bp.handle_right_type='AUTO'
lace_obj = bpy.data.objects.new('Lace', curve_data)
bpy.context.collection.objects.link(lace_obj)
lace_obj.data.materials.append(lace_mat)

# second lace side ending with missing aglet - simpler shorter frayed strand
lace_points2 = []
for i in range(n_pairs):
    side = -1 if i%2==0 else 1
    yy = 0.09 - i*0.028
    zz = 0.10 + i*0.012
    xx = side*0.03
    lace_points2.append((xx,yy,zz))
tailA = (-0.06, 0.09-6*0.028-0.05, 0.05)
lace_points2.append(tailA)
curve_data2 = bpy.data.curves.new('LaceCurve2', type='CURVE')
curve_data2.dimensions='3D'
curve_data2.bevel_depth=0.0025
curve_data2.bevel_resolution=4
poly2 = curve_data2.splines.new('BEZIER')
poly2.bezier_points.add(len(lace_points2)-1)
for i,p in enumerate(lace_points2):
    bp = poly2.bezier_points[i]
    bp.co = p
    bp.handle_left_type='AUTO'
    bp.handle_right_type='AUTO'
lace_obj2 = bpy.data.objects.new('Lace2', curve_data2)
bpy.context.collection.objects.link(lace_obj2)
lace_obj2.data.materials.append(lace_mat)

# frayed tip fibers - tiny thin cylinders at tail1/tailA
def add_fray(loc, n=6):
    frays=[]
    for i in range(n):
        bpy.ops.mesh.primitive_cylinder_add(radius=0.0006, depth=0.012, location=loc)
        f = bpy.context.active_object
        f.rotation_euler = (random.uniform(-0.4,0.4), random.uniform(-0.4,0.4), random.uniform(0,6.28))
        f.data.materials.append(lace_mat)
        frays.append(f)
    return frays

add_fray(tail2)
add_fray(tailA)

# ---------- HEEL PULL LOOP ----------
bpy.ops.mesh.primitive_torus_add(major_radius=0.018, minor_radius=0.005, major_segments=16, minor_segments=8)
loop = bpy.context.active_object
loop.name = "HeelPullLoop"
loop.location = (0, -0.255, 0.16)
loop.rotation_euler = (math.radians(90),0,0)
loop.data.materials.append(leather_welt)

# stitching around pull loop base
bpy.ops.mesh.primitive_cube_add(size=1)
looptab = bpy.context.active_object
looptab.scale=(0.02,0.008,0.03)
bpy.ops.object.transform_apply(scale=True)
looptab.location=(0,-0.255,0.145)
looptab.data.materials.append(leather_upper)

# ---------- PADDED COLLAR ----------
bpy.ops.mesh.primitive_torus_add(major_radius=0.058, minor_radius=0.014, major_segments=32, minor_segments=10)
collar = bpy.context.active_object
collar.name = "Collar"
collar.location = (0.005, -0.02, 0.195)
collar.rotation_euler = (0, math.radians(4), 0)
collar.scale = (1.0, 1.5, 0.6)
bpy.ops.object.transform_apply(scale=True)
collar.data.materials.append(leather_upper)

# ---------- JOIN UPPER-RELATED PARTS (keep sole separate for tread visibility already applied) ----------
bpy.ops.object.select_all(action='DESELECT')
for o in (boot_upper, tongue, collar, looptab):
    o.select_set(True)
bpy.context.view_layer.objects.active = boot_upper
bpy.ops.object.join()
boot_upper.name = "Boot_Upper_Combined"

# ---------- BACKDROP SWEEP ----------
bpy.ops.mesh.primitive_plane_add(size=4)
backdrop = bpy.context.active_object
backdrop.name = "Sweep"
me = backdrop.data
bm = bmesh.new()
bm.from_mesh(me)
bmesh.ops.subdivide_edges(bm, edges=bm.edges[:], cuts=20, use_grid_fill=True)
bm.to_mesh(me)
bm.free()
for v in backdrop.data.vertices:
    x,y,z = v.co
    if y < -1.0:
        t = (-1.0-y)
        v.co.z += t*t*0.5
backdrop.data.update()
backdrop.rotation_euler = (math.radians(90),0,0)
backdrop.location = (0,0.6,0)
backdrop.data.materials.append(backdrop_mat)
bpy.ops.object.shade_smooth()

# ---------- LIGHTING ----------
bpy.ops.object.light_add(type='AREA', location=(0.6,-0.6,0.6))
key = bpy.context.active_object
key.data.energy = 350
key.data.size = 0.8
key.rotation_euler = (math.radians(55), 0, math.radians(45))

bpy.ops.object.light_add(type='AREA', location=(-0.7,0.2,0.4))
fill = bpy.context.active_object
fill.data.energy = 90
fill.data.size = 0.7
fill.rotation_euler = (math.radians(70), 0, math.radians(-60))

bpy.ops.object.light_add(type='AREA', location=(0,-0.3,0.9))
rim = bpy.context.active_object
rim.data.energy = 120
rim.data.size = 0.4
rim.rotation_euler = (math.radians(10),0,0)

# ---------- CAMERA ----------
bpy.ops.object.camera_add(location=(0.42,-0.42,0.19))
cam = bpy.context.active_object
cam.name="Camera"
scene.camera = cam
direction = Vector((0,0,0.10)) - cam.location
rot_quat = direction.to_track_quat('-Z','Y')
cam.rotation_euler = rot_quat.to_euler()
cam.data.lens = 55
cam.data.dof.use_dof = True
cam.data.dof.focus_distance = (cam.location - Vector((0,0,0.10))).length
cam.data.dof.aperture_fstop = 3.2

# ---------- FINALIZE ----------
for o in bpy.data.objects:
    if o.type == 'MESH':
        o.select_set(False)

bpy.context.view_layer.update()

# Save file
import os
outpath = bpy.path.abspath("//worn_boot_scene.blend")
try:
    bpy.ops.wm.save_as_mainfile(filepath=outpath)
except Exception as e:
    print("Save failed:", e)
```