Files
samford-valley-home/source/samford-v008-tesla/replace_tesla.py

73 lines
5.3 KiB
Python

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.5<r['car_length']<4.9
assert .61<r['car_bounds'][2][0]<.65
assert len(old.objects)>10
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)