Files
Portfolio/assets/blender/evolution/refine-grand-tourer.py

95 lines
4.5 KiB
Python

"""Run in Blender after loading the original into the isolated Portfolio Evolution scene.
Original scene and other worktrees are never saved or overwritten.
"""
import bpy
from pathlib import Path
root_path = Path(__file__).resolve().parents[3]
scene = bpy.data.scenes['Portfolio Evolution']
bpy.context.window.scene = scene
car = next(o for o in scene.objects if o.name.split('.')[0] == 'chase-car')
materials = {m.name.split('.')[0]: m for o in scene.objects if o.type == 'MESH' for m in o.data.materials}
for name, color, metallic, roughness in [
('M_Body_Gunmetal', (0.14, 0.21, 0.28, 1), 0.78, 0.28),
('M_Body_Haunch', (0.19, 0.28, 0.34, 1), 0.8, 0.24),
('M_Wheel', (0.28, 0.34, 0.39, 1), 0.9, 0.23),
]:
bsdf = materials[name].node_tree.nodes.get('Principled BSDF')
bsdf.inputs['Base Color'].default_value = color
bsdf.inputs['Metallic'].default_value = metallic
bsdf.inputs['Roughness'].default_value = roughness
bsdf.inputs['Coat Weight'].default_value = 0.28 if 'Body' in name else 0
bsdf.inputs['Coat Roughness'].default_value = 0.23
# Recess the old projecting cylinders into the tire face.
for o in scene.objects:
stem = o.name.split('.')[0]
if '-rim-' in stem and '-ring-' not in stem:
o.scale.z *= 0.16
o.location.x = (1 if o.location.x > 0 else -1) * 1.145
if stem.startswith('diffuser-fin-'):
o.scale.z *= 0.65
def box(name, location, dimensions, material, bevel=0.012):
bpy.ops.mesh.primitive_cube_add(size=1, location=location)
obj = bpy.context.object
obj.name = name
obj.dimensions = dimensions
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
obj.parent = car
obj.data.materials.append(material)
mod = obj.modifiers.new('Machined edge', 'BEVEL')
mod.width = bevel
mod.segments = 3
obj.modifiers.new('Weighted normals', 'WEIGHTED_NORMAL')
return obj
# Six individually editable louvers, following the slope of the rear glass.
for index in range(6):
t = index / 5
y = 0.30 + t * 0.74
z = 1.245 - t * 0.275
box('evolution-louver-%02d' % index, (0, y, z), (1.25 + t * 0.18, 0.052, 0.026), materials['M_Lower_Graphite'], 0.008)
box('evolution-deck-lip', (0, 2.07, 0.89), (1.72, 0.095, 0.055), materials['M_Body_Haunch'], 0.018)
# Dark dividers articulate a continuous lamp without increasing emissive energy.
for x in [-0.54, -0.27, 0, 0.27, 0.54]:
box('evolution-lamp-divider', (x, 2.68, 0.694), (0.012, 0.012, 0.047), materials['M_Lower_Graphite'], 0.002)
# Write just this authored scene and its dependencies, not other open projects.
bpy.data.libraries.write(str(root_path / 'assets/blender/evolution/nightdrive-evolution.blend'), {scene}, fake_user=True, compress=True)
# A separate export scene combines geometry by material: eight runtime draw groups.
export_scene = bpy.data.scenes.new('Evolution Export')
bpy.context.window.scene = export_scene
root = bpy.data.objects.new('evolution-chase-car', None)
export_scene.collection.objects.link(root)
names = {'M_Body_Gunmetal': 'car-body', 'M_Body_Haunch': 'body-haunches', 'M_Glass_Smoke': 'rear-glass', 'M_Rear_Light': 'rear-light-bar', 'M_Cyan_Accent': 'car-underglow-source', 'M_Lower_Graphite': 'body-trim', 'M_Wheel': 'wheel-machining', 'M_Tire': 'tires'}
bpy.context.window.scene = scene
depsgraph = bpy.context.evaluated_depsgraph_get()
groups = {}
for obj in scene.objects:
if obj.type != 'MESH' or obj.name.startswith('LOOKDEV'):
continue
evaluated = obj.evaluated_get(depsgraph)
mesh = bpy.data.meshes.new_from_object(evaluated, depsgraph=depsgraph)
mesh.transform(obj.matrix_world)
clone = bpy.data.objects.new('export-' + obj.name, mesh)
export_scene.collection.objects.link(clone)
key = obj.data.materials[0].name.split('.')[0]
groups.setdefault(key, []).append(clone)
bpy.context.window.scene = export_scene
for key, objects in groups.items():
bpy.ops.object.select_all(action='DESELECT')
for obj in objects:
obj.select_set(True)
bpy.context.view_layer.objects.active = objects[0]
if len(objects) > 1:
bpy.ops.object.join()
joined = bpy.context.object
joined.name = 'evolution-' + names[key]
joined.parent = root
bpy.ops.object.select_all(action='SELECT')
bpy.ops.export_scene.gltf(filepath=str(root_path / 'src/assets/nightdrive-grand-tourer-evolution.glb'), export_format='GLB', use_selection=True, use_active_scene=True, export_apply=True, export_animations=False, export_cameras=False, export_lights=False)
print('Exported', len(groups), 'material groups')
bpy.context.window.scene = scene