"""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