"""Append the approved coachwork into its own scene; author only the new edition. Run inside Blender with __file__ pointing here. No open project is overwritten. """ import bpy, math from pathlib import Path from mathutils import Vector root_path = Path(__file__).resolve().parents[3] source = root_path / 'assets/blender/evolution/nightdrive-evolution.blend' with bpy.data.libraries.load(str(source), link=False) as (available, loaded): loaded.scenes = ['Portfolio Evolution'] scene = loaded.scenes[0] scene.name = 'Midnight Expressway' bpy.context.window.scene = scene car = next(o for o in scene.objects if o.type=='EMPTY' and o.name.startswith('chase-car')) materials = {m.name.split('.')[0]: m for o in scene.objects if o.type=='MESH' for m in o.data.materials} def finish(obj, name, material, bevel=0.007): obj.name = 'expressway-' + name obj.parent = car obj.data.materials.append(materials[material]) if bevel: mod = obj.modifiers.new('Machined radii', 'BEVEL'); mod.width=bevel; mod.segments=2 obj.modifiers.new('Surface normals', 'WEIGHTED_NORMAL') return obj def box(name, loc, dims, mat, bevel=0.007): bpy.ops.mesh.primitive_cube_add(size=1, location=loc) o=bpy.context.object; o.dimensions=dims bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) return finish(o,name,mat,bevel) def shell(name, vertices, faces, mat, bevel=0.005): mesh=bpy.data.meshes.new(name); mesh.from_pydata(vertices,[],faces); mesh.update() o=bpy.data.objects.new(name,mesh); scene.collection.objects.link(o) return finish(o,name,mat,bevel) def line(name, coords, mat, radius=0.004): curve=bpy.data.curves.new(name,'CURVE'); curve.dimensions='3D' curve.resolution_u=1; curve.bevel_depth=radius; curve.bevel_resolution=2 spline=curve.splines.new('POLY'); spline.points.add(len(coords)-1) for p,co in zip(spline.points,coords): p.co=(*co,1) obj=bpy.data.objects.new(name,curve); scene.collection.objects.link(obj) obj.parent=car; obj.data.materials.append(materials[mat]); obj.name='expressway-'+name return obj # Material story: cold painted metal, brushed alloy, smoke and deep graphite. for name,color,metal,rough in [ ('M_Body_Gunmetal',(0.18,0.28,0.36,1),0.72,0.27), ('M_Body_Haunch',(0.30,0.41,0.48,1),0.78,0.22), ('M_Lower_Graphite',(0.018,0.028,0.039,1),0.6,0.38), ('M_Wheel',(0.45,0.53,0.57,1),0.92,0.21), ('M_Tire',(0.009,0.012,0.016,1),0.0,0.9), ('M_Glass_Smoke',(0.014,0.045,0.065,1),0.36,0.14)]: bsdf=materials[name].node_tree.nodes.get('Principled BSDF') bsdf.inputs['Base Color'].default_value=color bsdf.inputs['Metallic'].default_value=metal; bsdf.inputs['Roughness'].default_value=rough if 'Body' in name: bsdf.inputs['Coat Weight'].default_value=0.42; bsdf.inputs['Coat Roughness'].default_value=0.19 # Replace the plain lamp with fourteen paired optical cells and lit outer returns. for obj in list(scene.objects): if obj.name.startswith(('rear-light-bar','evolution-lamp-divider','signature-turbine-vane')): bpy.data.objects.remove(obj,do_unlink=True) box('rear-optical-cassette',(0,2.684,0.700),(1.79,0.06,0.21),'M_Lower_Graphite',0.025) for side in [-1,1]: for i in range(7): x=side*(0.11+i*0.109) for z in [0.664,0.746]: box('rear-optic-cell',(x,2.720,z),(0.091,0.022,0.034),'M_Rear_Light',0.006) box('lamp-return',(side*0.852,2.704,0.705),(0.025,0.04,0.139),'M_Rear_Light',0.009) # Side intake recess, bevelled upper lip and three angled internal vanes. x=side*1.073 box('side-intake',(x,0.16,0.53),(0.027,1.03,0.255),'M_Lower_Graphite',0.033) box('intake-upper-lip',(side*1.093,0.13,0.670),(0.035,1.09,0.025),'M_Body_Haunch') for i in range(4): o=box('intake-vane',(side*1.095,-0.18+i*0.22,0.54),(0.025,0.022,0.21),'M_Wheel',0.003) o.rotation_euler.x=-0.32 # Deep lower blade has a distinct kicked-up rear end. poly=[(side*1.05,-1.03,0.24),(side*1.13,-1.03,0.20),(side*1.13,1.1,0.20),(side*1.11,1.30,0.36),(side*1.05,1.30,0.36)] shell('sculpted-skirt',poly,[tuple(range(5))],'M_Body_Haunch') line('skirt-channel',[(side*1.137,-0.8,0.224),(side*1.137,0.92,0.224),(side*1.117,1.17,0.31)],'M_Cyan_Accent',0.006) # Rear deck channel and restrained panel separation follow the wedge surfaces. line('rear-deck-seam',[(side*.72,1.12,.928),(side*.76,1.78,.876),(side*.85,2.25,.825)],'M_Lower_Graphite',0.006) line('haunch-pinstripe',[(side*.91,1.2,.905),(side*1.005,1.67,.857),(side*.997,2.20,.759)],'M_Wheel',0.0035) for i in range(4): box('deck-extraction-slot',(side*.49,1.36+i*.116,.906-i*.012),(.31,.039,.018),'M_Lower_Graphite',0.009) # Larger aerofoil endplates, kinked with a recessed coral slit. box('wing-endplate-shell',(side*1.195,2.35,1.268),(.032,.43,.24),'M_Body_Haunch',.016) box('wing-endplate-inlay',(side*1.213,2.39,1.28),(.009,.22,.025),'M_Rear_Light',.004) # Exhausts inside the graphite recess, with dark cores instead of solid cylinders. for x0 in [0.29,0.43]: x=side*x0 box('rear-exhaust-surround',(x,2.7,.37),(.105,.115,.085),'M_Wheel',.018) box('rear-exhaust-core',(x,2.762,.37),(.079,.01,.059),'M_Lower_Graphite',.014) for i in range(5): box('rear-cooling-slot',(side*(.53+i*.06),2.704,.497),(.028,.018,.064),'M_Lower_Graphite',.005) # Diffuser undertray: channels and an alloy trailing edge remain readable at speed. line('diffuser-frame',[(-.82,2.68,.29),(-.63,2.78,.24),(.63,2.78,.24),(.82,2.68,.29)],'M_Wheel',.009) for x in [-.66,-.33,0,.33,.66]: shell('diffuser-strake',[(x,2.25,.22),(x,2.74,.26),(x,2.78,.105),(x,2.36,.105)],[(0,1,2,3)],'M_Lower_Graphite',0) # Turbine machining has a dished barrel, swept spokes and a shoulder ring. for side in [-1,1]: for axle,y,z,radius in [('front',-1.62,.42,.42),('rear',1.62,.46,.46)]: label=f'wheel-{axle}-{"left" if side<0 else "right"}' for rad,xoff,thick,mat in [(radius-.016,1.153,.009,'M_Tire'),(.282 if axle=='rear' else .26,1.181,.008,'M_Wheel')]: coords=[(side*xoff,y+math.sin(i*math.tau/64)*rad,z+math.cos(i*math.tau/64)*rad) for i in range(65)] line(label+'-shoulder',coords,mat,thick) for i in range(10): angle=i*math.tau/10 verts=[] for rad,a,xoff in [(.075,angle,1.17),(.255,angle+.30,1.193),(.263,angle+.48,1.193),(.088,angle+.35,1.17)]: verts.append((side*xoff,y+math.sin(a)*rad,z+math.cos(a)*rad)) shell(label+'-turbine',verts,[(0,1,2,3)],'M_Wheel',.004) bpy.ops.mesh.primitive_uv_sphere_add(segments=16,ring_count=8,radius=1,location=(side*1.20,y,z)) hub=bpy.context.object; hub.scale=(.025,.064,.064) bpy.ops.object.transform_apply(location=False,rotation=False,scale=True) finish(hub,label+'-hub','M_Lower_Graphite',0) # Write only this scene and its own dependencies, retaining individually editable objects. output=root_path/'assets/blender/expressway/nightdrive-expressway.blend' bpy.data.libraries.write(str(output),{scene},fake_user=True,compress=True) print('AUTHORED',scene.name,len(scene.objects),'objects',output)