import bpy,json,math from pathlib import Path from mathutils import Matrix,Vector P=Path(__file__).resolve().parents[1] scene=bpy.context.scene;detail=bpy.data.collections['DETAIL - full geometry for renders'];fast=bpy.data.collections['00 FAST TOUR - lightweight viewport'];detail.hide_viewport=False;fast.hide_viewport=True scene.frame_set(1);bpy.context.view_layer.update() old=bpy.data.collections['29 Car and bicycles'];removed=[o.name for o in old.objects if o.name.lower().startswith('tesla')] for n in removed:bpy.data.objects.remove(bpy.data.objects[n],do_unlink=True) oldproxy=bpy.data.objects.get('FAST | 29 Car and bicycles') if oldproxy:bpy.data.objects.remove(oldproxy,do_unlink=True) with bpy.data.libraries.load(str(P/'private-assets/7K-Studio-Tesla-Model-3.blend'),link=False) as (src,dst):dst.objects=src.objects car=bpy.data.collections.new('38 Licensed - Tesla Model 3 by 7K Studio');detail.children.link(car) for o in dst.objects: if o:car.objects.link(o);o.name='7K Tesla | '+o.name root=next(o for o in car.objects if o.type=='EMPTY' and o.parent is None) root.location=(-1.7,-19.1,.631);root['creator']='7K Studio';root['license']='BlenderKit Royalty Free - keep out of public source distribution';root['asset_id']='1fc56739-2fd9-447e-b2c1-fee5d9e276b7' bpy.context.view_layer.update() # Keep original artist geometry/shaders. Limit only the viewport subdivision cost. for o in car.objects: for mod in o.modifiers: if mod.type=='SUBSURF':mod.levels=min(mod.levels,1);mod.render_levels=min(mod.render_levels,2) # Static evaluated copy preserves source UVs and materials for fast viewport and export. def proxy(collection,name,ratio=1): deps=bpy.context.evaluated_depsgraph_get();copies=[] for o in collection.objects: if o.type not in {'MESH','CURVE','FONT'}:continue ev=o.evaluated_get(deps);me=bpy.data.meshes.new_from_object(ev,depsgraph=deps);ob=bpy.data.objects.new('temporary proxy',me);fast.objects.link(ob);ob.matrix_world=o.matrix_world.copy();copies.append(ob) # Resolve original material datablocks, never keep evaluated IDs. mats=[bpy.data.materials.get(m.name) if m else None for m in me.materials];me.materials.clear() for m in mats: if m:me.materials.append(m) bpy.ops.object.select_all(action='DESELECT') for o in copies:o.select_set(True) bpy.context.view_layer.objects.active=copies[0];bpy.ops.object.join();ob=copies[0];ob.name=name bpy.ops.object.transform_apply(location=True,rotation=True,scale=True) if ratio<1: d=ob.modifiers.new('Viewport simplification','DECIMATE');d.ratio=ratio;bpy.ops.object.modifier_apply(modifier=d.name) return ob fast.hide_viewport=False bikeproxy=proxy(old,'FAST | 29 Bicycles and racks') teslaproxy=proxy(car,'FAST | 38 Licensed Tesla Model 3',.35) fast.hide_viewport=True # Coil cable on the wall hook instead of leaving a plug floating at the old car. for o in list(bpy.data.collections['28 Garage and workbench'].objects): if 'charge' in o.name.lower() or 'cable' in o.name.lower():print('CHARGER',o.name,flush=True) plug=bpy.data.objects.get('Tesla plugged charge connector') if plug:plug.location=(-3.66,-20.8,1.77) # Additional camera does not alter the existing animated 31-stop tour. camdata=bpy.data.cameras.new('Tesla garage detail');cam=bpy.data.objects.new('VIEW - 7K Tesla garage',camdata);scene.collection.objects.link(cam);cam.location=(-5.1,-12.1,2.55);target=Vector((-1.0,-19.25,1.40));cam.rotation_euler=(target-cam.location).to_track_quat('-Z','Y').to_euler();camdata.lens=39 scene['vehicle_asset']='Tesla Model 3 by 7K Studio; BlenderKit asset 1fc56739-2fd9-447e-b2c1-fee5d9e276b7';scene['revision']='v008 licensed Tesla replacement';scene['public_distribution']='Do not publish this .blend or extracted car; use public source without licensed asset.' # Record exact source objects for the narrow Unreal replacement. pts=[o.matrix_world@Vector(v) for o in car.objects if o.type=='MESH' for v in o.bound_box] r={'removed_placeholder_objects':removed,'artist_objects':len(car.objects),'car_bounds':[[min(v[k] for v in pts),max(v[k] for v in pts)] for k in range(3)],'car_length':max(v.y for v in pts)-min(v.y for v in pts),'fast_car_polygons':len(teslaproxy.data.polygons),'bicycles_preserved':len(old.objects),'source_asset':'1fc56739-2fd9-447e-b2c1-fee5d9e276b7'} (P/'blender-replacement-checks.json').write_text(json.dumps(r,indent=2)) assert 4.510 bpy.ops.file.pack_all() # Save user-ready file with fast display and the original tour camera active. fast.hide_viewport=False;detail.hide_viewport=True bpy.ops.wm.save_as_mainfile(filepath=str(P/'samford-solarpunk-tesla-v008.blend')) # Render full geometry in the dedicated garage camera. detail.hide_viewport=False;fast.hide_viewport=True;scene.camera=cam scene.render.engine='CYCLES';scene.render.resolution_x=1600;scene.render.resolution_y=1000;scene.render.resolution_percentage=100;scene.cycles.samples=40;scene.cycles.use_denoising=True prefs=bpy.context.preferences.addons['cycles'].preferences try: prefs.compute_device_type='METAL';prefs.get_devices() for d in prefs.devices:d.use=d.type=='METAL' scene.cycles.device='GPU' except Exception:pass scene.render.image_settings.file_format='PNG';scene.render.filepath=str(P/'renders/tesla-garage.png');bpy.ops.render.render(write_still=True) print('TESLA_REPLACEMENT_READY',flush=True)