73 lines
5.3 KiB
Python
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)
|