Author physical road furniture, layered terrain and atmosphere, projected illuminated route signs, and detailed Blender coachwork. Refine camera mass, wheel contact, chapter composition, and compact reading space while preserving approved content and audio contracts. Include isolated Blender source, before/after and hardware Chrome motion evidence, preservation hashes, validated preview checks, and documented inherited unit failures.
125 lines
7.0 KiB
Python
125 lines
7.0 KiB
Python
"""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)
|