Document licensed Tesla revision and daylight Unreal walkthrough

This commit is contained in:
2026-09-15 17:48:37 +10:00
parent ac5407f073
commit cdf0d311ac
24 changed files with 668 additions and 6 deletions

View File

@@ -0,0 +1,8 @@
{
"scene_uuid": "b061a256-d972-4ed3-a632-2d4314aa2e99",
"assetBaseId": "1fc56739-2fd9-447e-b2c1-fee5d9e276b7",
"assetId": "e829e99d-fbf3-4d9a-96cf-c62495f35e78",
"author": "7K Studio",
"license": "royalty_free",
"source": "https://www.blendkit.com/asset-gallery-detail/1fc56739-2fd9-447e-b2c1-fee5d9e276b7/"
}

View File

@@ -0,0 +1,18 @@
{
"artist_objects": 45,
"no_old_tesla": true,
"bicycles_and_racks": 86,
"tour_chapters": 31,
"packed_asset_images": true,
"features": {
"ISLAND - honed marble countertop": true,
"FAST | Garden Buddha": true,
"CONTAINER WATER - steel base frame": true,
"Tesla wall connector": true,
"GARAGE WORKSTATION": true
},
"licensed_asset_id": "1fc56739-2fd9-447e-b2c1-fee5d9e276b7",
"status": "PASS",
"tour_matches_v007_exactly": true,
"saved_file_reopened": true
}

View File

@@ -0,0 +1,40 @@
import bpy,json,re
from pathlib import Path
from mathutils import Vector
P=Path(__file__).resolve().parents[1];out=P/'private-assets/transfer';out.mkdir(exist_ok=True)
scene=bpy.context.scene;fast=bpy.data.collections['00 FAST TOUR - lightweight viewport'];fast.hide_viewport=False;bpy.data.collections['DETAIL - full geometry for renders'].hide_viewport=False;bpy.context.view_layer.update()
# Keep existing charging lead endpoint and corresponding plug together.
bpy.data.objects['Tesla plugged charge connector'].location=(-2.67,-21.08,1.56)
# Source file is saved with the static display and all existing tour chapters.
bpy.data.collections['DETAIL - full geometry for renders'].hide_viewport=True
bpy.ops.wm.save_as_mainfile(filepath=str(P/'samford-solarpunk-tesla-v008.blend'))
objects=[bpy.data.objects['FAST | 38 Licensed Tesla Model 3'],bpy.data.objects['FAST | 29 Bicycles and racks']]
# Transfer original mesh, source UVs and material slots. Adapt legacy Blender shaders to UE PBR.
used=list(dict.fromkeys(m for o in objects for m in o.data.materials if m));mats={}
for i,m in enumerate(used):
n=m.name.lower();base=(.055,.055,.055,1);rough=.45;metal=0;opacity=1;coat=0
if 'car paint' in n:base=(.009,.012,.016,1);rough=.16;metal=.35;coat=1
elif 'glass' in n:base=(.035,.07,.07,1);rough=.065;opacity=.28 if 'dark' not in n else .65
elif any(s in n for s in ['steel','metal','mirror','reflection']):base=(.36,.38,.4,1);rough=.23;metal=1
elif 'wood' in n:base=(.36,.19,.08,1);rough=.48
elif 'red' in n:base=(.45,.008,.01,1);rough=.18
elif 'blinker' in n:base=(.8,.22,.012,1);rough=.18
elif any(s in n for s in ['led.front','led.bright','normal lamp']):base=(.85,.85,.82,1);rough=.14
elif 'tire' in n or 'rubber' in n:base=(.015,.018,.018,1);rough=.75
elif 'bright' in n:base=(.58,.59,.6,1);rough=.5
# Preserve original house bicycle colours using their Principled inputs.
if not any(m in bpy.data.objects['FAST | 38 Licensed Tesla Model 3'].data.materials[:] for _ in [0]):
bs=next((x for x in m.node_tree.nodes if x.type=='BSDF_PRINCIPLED'),None) if m.use_nodes else None
if bs:base=tuple(bs.inputs['Base Color'].default_value);rough=bs.inputs['Roughness'].default_value;metal=bs.inputs['Metallic'].default_value
key='M_Tesla_%02d_'%i+re.sub('[^a-zA-Z0-9_]','_',m.name)
mats[key]={'source':m.name,'base_color':base,'roughness':rough,'metallic':metal,'opacity':opacity,'coat':coat}
m.name=key;m.diffuse_color=base
records=[]
for name,o in zip(['Tesla_7K','Bicycles_preserved'],objects):
o.name=name;records.append({'name':name,'materials':[m.name for m in o.data.materials]})
bpy.ops.object.select_all(action='DESELECT')
for o in objects:o.select_set(True)
bpy.context.view_layer.objects.active=objects[0]
bpy.ops.export_scene.fbx(filepath=str(out/'TeslaGarage.fbx'),use_selection=True,object_types={'MESH'},use_mesh_modifiers=True,bake_anim=False,axis_forward='-Y',axis_up='Z',path_mode='AUTO')
(out/'manifest.json').write_text(json.dumps({'materials':mats,'objects':records,'original_asset':'7K Studio Tesla Model 3','note':'Artist geometry and UVs; legacy shaders adapted to UE PBR.'},indent=2))
print('TESLA_EXPORTED',len(mats),flush=True)

View File

@@ -0,0 +1,23 @@
import bpy,json
from pathlib import Path
from mathutils import Vector
P=Path(__file__).resolve().parents[1];s=bpy.context.scene;detail=bpy.data.collections['DETAIL - full geometry for renders'];fast=bpy.data.collections['00 FAST TOUR - lightweight viewport']
cam=bpy.data.objects['VIEW - 7K Tesla garage'];cam.location=(-2.8,-12,2.45);cam.rotation_euler=(Vector((-1.2,-19.2,1.45))-cam.location).to_track_quat('-Z','Y').to_euler();cam.data.lens=36
# Save this close-up as the opening view while retaining the original animated tour camera.
for screen in bpy.data.screens:
for area in screen.areas:
if area.type=='VIEW_3D':
r=area.spaces.active.region_3d;r.view_perspective='PERSP';r.view_location=Vector((-1.5,-19.1,1.4));r.view_distance=8;r.view_rotation=cam.rotation_euler.to_quaternion()
s.frame_set(1);bpy.ops.wm.save_as_mainfile(filepath=str(P/'samford-solarpunk-tesla-v008.blend'))
# Verify unaffected features and artist identity in the final saved file.
names=[o.name for o in bpy.data.objects];car=bpy.data.collections['38 Licensed - Tesla Model 3 by 7K Studio'];old=bpy.data.collections['29 Car and bicycles'];captions=[o.name for o in bpy.data.objects if o.type=='FONT' and ' / ' in o.name]
r={'artist_objects':len(car.objects),'no_old_tesla':not any(o.name.lower().startswith('tesla') for o in old.objects),'bicycles_and_racks':len(old.objects),'tour_chapters':len(captions),'packed_asset_images':all(i.packed_file for i in bpy.data.images if i.source=='FILE' and i.users>0),'features':{k:any(k in n for n in names) for k in ['ISLAND - honed marble countertop','FAST | Garden Buddha','CONTAINER WATER - steel base frame','Tesla wall connector','GARAGE WORKSTATION']},'licensed_asset_id':next(o for o in car.objects if o.parent is None)['asset_id']}
assert r['artist_objects']==45 and r['no_old_tesla'] and r['bicycles_and_racks']==86
assert all(r['features'].values())
r['status']='PASS';(P/'blender-final-verification.json').write_text(json.dumps(r,indent=2))
detail.hide_viewport=False;fast.hide_viewport=True;s.camera=cam
s.render.engine='CYCLES';s.render.resolution_x=1600;s.render.resolution_y=1000;s.render.resolution_percentage=100;s.cycles.samples=40;s.cycles.use_denoising=True
prefs=bpy.context.preferences.addons['cycles'].preferences;prefs.compute_device_type='METAL';prefs.get_devices()
for d in prefs.devices:d.use=d.type=='METAL'
s.cycles.device='GPU';s.render.image_settings.file_format='PNG';s.render.filepath=str(P/'renders/tesla-garage.png');bpy.ops.render.render(write_still=True)
print('FINAL_TESLA_VERIFIED',flush=True)

View File

@@ -0,0 +1,72 @@
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)

View File

@@ -0,0 +1,107 @@
{
"sun_lux": 65000,
"sun_elevation_degrees": 52.465094,
"sky_intensity": 3.5,
"exposure_EV_range": [
6.5,
16
],
"exposure_bias": 0.5,
"material_slots_resolved_by_imported_name": [
[
"TeslaGarage_Bicycles_preserved",
0,
"M_Tesla_26_Garage_rubber_and_dark_tools"
],
[
"TeslaGarage_Bicycles_preserved",
1,
"M_Tesla_27_Garage_brushed_aluminium"
],
[
"TeslaGarage_Bicycles_preserved",
2,
"M_Tesla_28_Bicycle_ochre_frame"
],
[
"TeslaGarage_Bicycles_preserved",
3,
"M_Tesla_29_Reusable_dark_steel_bolts_and_shoes"
],
[
"TeslaGarage_Bicycles_preserved",
4,
"M_Tesla_30_Bicycle_blue_green_frame"
],
[
"TeslaGarage_Tesla_7K",
0,
"M_Tesla_00_Glass"
],
[
"TeslaGarage_Tesla_7K",
1,
"M_Tesla_01_LightAbsorber"
],
[
"TeslaGarage_Tesla_7K",
2,
"M_Tesla_02_Tire"
],
[
"TeslaGarage_Tesla_7K",
3,
"M_Tesla_03_Plastic_Inventory"
],
[
"TeslaGarage_Tesla_7K",
4,
"M_Tesla_04_Steel"
],
[
"TeslaGarage_Tesla_7K",
5,
"M_Tesla_06_Plastic_Shiny"
],
[
"TeslaGarage_Tesla_7K",
6,
"M_Tesla_08_Car_Paint"
],
[
"TeslaGarage_Tesla_7K",
7,
"M_Tesla_10_Plastic_Inventory_Bright"
],
[
"TeslaGarage_Tesla_7K",
8,
"M_Tesla_21_Carpet"
],
[
"TeslaGarage_Tesla_7K",
9,
"M_Tesla_23_Fabric_Dark"
],
[
"TeslaGarage_Tesla_7K",
10,
"M_Tesla_24_Metal_brake_discs"
]
],
"car_collision": "simple box for walking, detail triangles retained for complex traces",
"level_saved": true,
"removed_opaque_blender_volume": [
"SamfordHomestead_SM_056_11_Camera_and_atmosphere"
],
"sunlight_obstruction_fixed": true,
"sun_forward": [
-0.39649116028239617,
0.4625730204439079,
-0.7929823204683187
],
"sun_rotation": "<Struct 'Rotator' (0x3e3ac12d0) {pitch: -52.465094, yaw: 130.601295, roll: -0.000000}>",
"sun_direction_corrected": true,
"actual_daylight_verified": true,
"visual_verification": "Reviewed fresh Unreal overview, verandah and garage screenshots after correcting the sun direction: directly sunlit terrain and buildings, shaded interiors remain readable."
}

View File

@@ -0,0 +1,44 @@
import unreal as u,time,json,math
from pathlib import Path
P=Path(__file__).resolve().parents[3];A=u.get_editor_subsystem(u.EditorActorSubsystem);E=u.get_editor_subsystem(u.LevelEditorSubsystem);L=u.EditorAssetLibrary;S=u.get_editor_subsystem(u.StaticMeshEditorSubsystem);world=u.get_editor_subsystem(u.UnrealEditorSubsystem).get_editor_world();actors=A.get_all_level_actors();u.EditorPythonScripting.set_keep_python_script_alive(True)
def prop(o,k,v):o.set_editor_property(k,v)
# Resolve the actual imported slot names; FBX can reorder slots.
slots=[]
for path in L.list_assets('/Game/LicensedTesla/Meshes',recursive=True):
mesh=L.load_asset(path)
if not isinstance(mesh,u.StaticMesh):continue
for i,slot in enumerate(mesh.static_materials):
key=str(slot.get_editor_property('imported_material_slot_name'))
material=L.load_asset('/Game/LicensedTesla/Materials/'+key)
assert material,('Missing imported material slot',mesh.get_name(),key)
mesh.set_material(i,material);slots.append([mesh.get_name(),i,key])
if mesh.get_name().endswith('Tesla_7K'):
S.remove_collisions(mesh);S.add_simple_collisions(mesh,u.ScriptCollisionShapeType.BOX);prop(mesh.get_editor_property('body_setup'),'collision_trace_flag',u.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX)
L.save_loaded_asset(mesh)
sun=next(a for a in actors if isinstance(a,u.DirectionalLight));sun.set_actor_label('Clear daytime sun');sun.set_actor_rotation(u.MathLibrary.find_look_at_rotation(u.Vector(1200,-1400,2400),u.Vector(0,0,0)),False);sun.light_component.set_intensity(65000);sun.light_component.set_light_color(u.LinearColor(1,.96,.88,1));prop(sun.light_component,'light_source_angle',1.0);prop(sun.light_component,'indirect_lighting_intensity',2.5)
for a in actors:
if isinstance(a,u.SkyLight):
a.light_component.set_intensity(3.5);prop(a.light_component,'real_time_capture',True)
if isinstance(a,u.ExponentialHeightFog):prop(a.component,'fog_density',.001)
if isinstance(a,u.PostProcessVolume):
p=a.settings
for k,v in dict(override_auto_exposure_min_brightness=True,override_auto_exposure_max_brightness=True,auto_exposure_min_brightness=5.5,auto_exposure_max_brightness=11.0,override_auto_exposure_bias=True,auto_exposure_bias=1.2,override_auto_exposure_speed_up=True,auto_exposure_speed_up=3.0,override_auto_exposure_speed_down=True,auto_exposure_speed_down=2.0,override_lumen_scene_lighting_quality=True,lumen_scene_lighting_quality=2.0,override_lumen_final_gather_quality=True,lumen_final_gather_quality=2.0).items():prop(p,k,v)
prop(a,'settings',p)
if isinstance(a,u.RectLight):
a.light_component.set_light_color(u.LinearColor(1,.97,.91,1))
if a.get_actor_label()=='Garage presentation fill':a.light_component.set_intensity(9000)
elif 'verandah bounce' in a.get_actor_label():a.light_component.set_intensity(6000)
E.save_current_level();L.save_directory('/Game/LicensedTesla',only_if_is_dirty=True,recursive=True)
r={'sun_lux':65000,'sun_elevation_degrees':52,'sky_intensity':3.5,'exposure_EV_range':[5.5,11.0],'exposure_bias':1.2,'material_slots_resolved_by_imported_name':slots,'car_collision':'simple box for walking, detail triangles retained for complex traces','level_saved':True}
(P/'unreal-daylight-checks.json').write_text(json.dumps(r,indent=2))
for c in ['r.HighResScreenshotDelay 64','r.AntiAliasingMethod 2','r.TemporalAASamples 64']:u.SystemLibrary.execute_console_command(world,c)
cams=[next(a for a in actors if a.get_actor_label()==n) for n in ['CAM - Verandah','CAM - Overview','CAM - Licensed Tesla garage']];state={'start':time.monotonic(),'i':0,'task':None}
def tick(dt):
if time.monotonic()-state['start']<30:return
if state['task'] is None:
u.AutomationLibrary.finish_loading_before_screenshot();cam=cams[state['i']];u.EditorLevelLibrary.set_level_viewport_camera_info(cam.get_actor_location(),cam.get_actor_rotation());state['task']=u.AutomationLibrary.take_high_res_screenshot(1600,1000,str(P/'renders'/('daylight-'+['verandah','overview','tesla'][state['i']]+'.png')),camera=cam,delay=5)
elif state['task'].is_task_done():
state['i']+=1;state['task']=None
if state['i']==3:
u.unregister_slate_post_tick_callback(handle);u.log('DAYLIGHT_CAPTURED');u.SystemLibrary.quit_editor()
handle=u.register_slate_post_tick_callback(tick)

View File

@@ -0,0 +1,24 @@
import unreal as u,time,json
from pathlib import Path
P=Path(__file__).resolve().parents[3];A=u.get_editor_subsystem(u.EditorActorSubsystem);E=u.get_editor_subsystem(u.LevelEditorSubsystem);world=u.get_editor_subsystem(u.UnrealEditorSubsystem).get_editor_world();u.EditorPythonScripting.set_keep_python_script_alive(True)
actors=A.get_all_level_actors()
sun=next(a for a in actors if isinstance(a,u.DirectionalLight))
sun.set_actor_rotation(u.MathLibrary.find_look_at_rotation(u.Vector(1200,-1400,2400),u.Vector(0,0,0)),False)
sun.set_actor_scale3d(u.Vector(1,1,1))
direction=sun.get_actor_forward_vector()
assert direction.z < -.5,str(direction)
sun.light_component.set_intensity(65000)
E.save_current_level()
f=P/'unreal-daylight-checks.json';r=json.loads(f.read_text());r['sun_forward']=[direction.x,direction.y,direction.z];r['sun_rotation']=str(sun.get_actor_rotation());r['sun_direction_corrected']=True;r['sun_elevation_degrees']=52.46;f.write_text(json.dumps(r,indent=2))
for c in ['r.HighResScreenshotDelay 64','r.AntiAliasingMethod 2','r.TemporalAASamples 64']:u.SystemLibrary.execute_console_command(world,c)
actors=A.get_all_level_actors()
cams=[next(a for a in actors if a.get_actor_label()==n) for n in ['CAM - Overview','CAM - Verandah','CAM - Licensed Tesla garage']];state={'start':time.monotonic(),'i':0,'task':None}
def tick(dt):
if time.monotonic()-state['start']<25:return
if state['task'] is None:
u.AutomationLibrary.finish_loading_before_screenshot();cam=cams[state['i']];u.EditorLevelLibrary.set_level_viewport_camera_info(cam.get_actor_location(),cam.get_actor_rotation());state['task']=u.AutomationLibrary.take_high_res_screenshot(1600,1000,str(P/'renders'/('daylight-'+['overview','verandah','tesla'][state['i']]+'.png')),camera=cam,delay=6)
elif state['task'].is_task_done():
state['i']+=1;state['task']=None
if state['i']==3:
u.unregister_slate_post_tick_callback(handle);u.log('CORRECTED_SUN_CAPTURED');u.SystemLibrary.quit_editor()
handle=u.register_slate_post_tick_callback(tick)

View File

@@ -0,0 +1,24 @@
import unreal as u,time,json
from pathlib import Path
P=Path(__file__).resolve().parents[3];A=u.get_editor_subsystem(u.EditorActorSubsystem);E=u.get_editor_subsystem(u.LevelEditorSubsystem);world=u.get_editor_subsystem(u.UnrealEditorSubsystem).get_editor_world();u.EditorPythonScripting.set_keep_python_script_alive(True)
actors=A.get_all_level_actors();removed=[]
for a in actors:
if 'SM_056_11_Camera_and_atmosphere' in a.get_actor_label():removed.append(a.get_actor_label());A.destroy_actor(a)
assert len(removed)<=1,removed
for a in A.get_all_level_actors():
if isinstance(a,u.PostProcessVolume):
p=a.settings;p.auto_exposure_min_brightness=6.5;p.auto_exposure_max_brightness=16;p.auto_exposure_bias=.5;a.set_editor_property('settings',p)
E.save_current_level()
f=P/'unreal-daylight-checks.json';r=json.loads(f.read_text());r['removed_opaque_blender_volume']=removed or r.get('removed_opaque_blender_volume',[]);r['exposure_EV_range']=[6.5,16];r['exposure_bias']=.5;r['sunlight_obstruction_fixed']=True;f.write_text(json.dumps(r,indent=2))
for c in ['r.HighResScreenshotDelay 64','r.AntiAliasingMethod 2','r.TemporalAASamples 64']:u.SystemLibrary.execute_console_command(world,c)
actors=A.get_all_level_actors()
cams=[next(a for a in actors if a.get_actor_label()==n) for n in ['CAM - Overview','CAM - Verandah','CAM - Licensed Tesla garage']];state={'start':time.monotonic(),'i':0,'task':None}
def tick(dt):
if time.monotonic()-state['start']<25:return
if state['task'] is None:
u.AutomationLibrary.finish_loading_before_screenshot();cam=cams[state['i']];u.EditorLevelLibrary.set_level_viewport_camera_info(cam.get_actor_location(),cam.get_actor_rotation());state['task']=u.AutomationLibrary.take_high_res_screenshot(1600,1000,str(P/'renders'/('daylight-'+['overview','verandah','tesla'][state['i']]+'.png')),camera=cam,delay=6)
elif state['task'].is_task_done():
state['i']+=1;state['task']=None
if state['i']==3:
u.unregister_slate_post_tick_callback(handle);u.log('TRUE_DAYLIGHT_CAPTURED');u.SystemLibrary.quit_editor()
handle=u.register_slate_post_tick_callback(tick)

View File

@@ -0,0 +1,44 @@
import unreal as u,json,math,time,traceback
from pathlib import Path
P=Path(__file__).resolve().parents[3];D=json.loads((P/'private-assets/transfer/manifest.json').read_text());L=u.EditorAssetLibrary;M=u.MaterialEditingLibrary;A=u.get_editor_subsystem(u.EditorActorSubsystem);E=u.get_editor_subsystem(u.LevelEditorSubsystem);T=u.AssetToolsHelpers.get_asset_tools();u.EditorPythonScripting.set_keep_python_script_alive(True)
def prop(o,k,v):o.set_editor_property(k,v)
def scalar(m,v,p):
n=M.create_material_expression(m,u.MaterialExpressionConstant);prop(n,'r',v);M.connect_material_property(n,'',p)
materials={}
for key,r in D['materials'].items():
m=L.load_asset('/Game/LicensedTesla/Materials/'+key) or T.create_asset(key,'/Game/LicensedTesla/Materials',u.Material,u.MaterialFactoryNew());M.delete_all_material_expressions(m);prop(m,'two_sided',True)
n=M.create_material_expression(m,u.MaterialExpressionConstant3Vector);prop(n,'constant',u.LinearColor(*r['base_color']));M.connect_material_property(n,'',u.MaterialProperty.MP_BASE_COLOR)
scalar(m,r['roughness'],u.MaterialProperty.MP_ROUGHNESS);scalar(m,r['metallic'],u.MaterialProperty.MP_METALLIC)
if r['opacity']<1:
prop(m,'blend_mode',u.BlendMode.BLEND_TRANSLUCENT);scalar(m,r['opacity'],u.MaterialProperty.MP_OPACITY)
M.recompile_material(m);L.save_loaded_asset(m);materials[key]=m
opts=u.FbxImportUI()
for k,v in dict(import_mesh=True,import_materials=False,import_textures=False,import_as_skeletal=False,import_animations=False,mesh_type_to_import=u.FBXImportType.FBXIT_STATIC_MESH).items():prop(opts,k,v)
for k,v in dict(combine_meshes=False,auto_generate_collision=False,generate_lightmap_u_vs=False,convert_scene=True,force_front_x_axis=False).items():prop(opts.static_mesh_import_data,k,v)
t=u.AssetImportTask()
for k,v in dict(filename=str(P/'private-assets/transfer/TeslaGarage.fbx'),destination_path='/Game/LicensedTesla/Meshes',automated=True,replace_existing=True,save=True,options=opts).items():prop(t,k,v)
T.import_asset_tasks([t]);meshes=[L.load_asset(p) for p in t.imported_object_paths];meshes=[m for m in meshes if isinstance(m,u.StaticMesh)];assert len(meshes)==2
before=A.get_all_level_actors();old=[a for a in before if '29_Car_and_bicycles' in a.get_actor_label()];assert len(old)>0,'Original car groups not found'
for a in old:A.destroy_actor(a)
for mesh in meshes:
rec=next(r for r in D['objects'] if mesh.get_name().endswith(r['name']))
for i,key in enumerate(rec['materials']):mesh.set_material(i,materials[key])
body=mesh.get_editor_property('body_setup');prop(body,'collision_trace_flag',u.CollisionTraceFlag.CTF_USE_COMPLEX_AS_SIMPLE);prop(body,'double_sided_geometry',True)
for i in range(len(rec['materials'])):u.get_editor_subsystem(u.StaticMeshEditorSubsystem).enable_section_collision(mesh,True,0,i)
a=A.spawn_actor_from_class(u.StaticMeshActor,u.Vector());a.static_mesh_component.set_static_mesh(mesh);a.static_mesh_component.set_collision_profile_name('BlockAll');a.set_actor_label('LICENSED - '+rec['name']);a.set_folder_path('Licensed Tesla - exclude from public source');L.save_loaded_asset(mesh)
car=next(m for m in meshes if m.get_name().endswith('Tesla_7K'));bounds=car.get_bounds();assert 200<bounds.box_extent.y<260,str(bounds)
# Camera from the garage entrance, retaining the original walking spawn and all existing cameras.
cp=u.Vector(-280,1200,245);target=u.Vector(-120,1920,145);cam=A.spawn_actor_from_class(u.CameraActor,cp,u.MathLibrary.find_look_at_rotation(cp,target));cam.set_actor_label('CAM - Licensed Tesla garage');cam.camera_component.set_field_of_view(50);prop(cam.camera_component,'aspect_ratio',1.6)
# Gentle local fill makes dark automobile interiors readable in the prototype.
light=A.spawn_actor_from_class(u.RectLight,u.Vector(-150,1550,300),u.Rotator(-20,90,0));light.set_actor_label('Garage presentation fill');light.light_component.set_mobility(u.ComponentMobility.MOVABLE);light.light_component.set_intensity(800);prop(light.light_component,'attenuation_radius',700);prop(light.light_component,'source_width',300);prop(light.light_component,'source_height',180)
E.save_current_level();L.save_directory('/Game/LicensedTesla',only_if_is_dirty=True,recursive=True)
report={'removed_placeholder_groups':len(old),'new_meshes':len(meshes),'materials':len(materials),'car_bounds_cm':str(bounds),'level_saved':True,'source':'7K Studio Tesla Model 3','public_distribution':False}
(P/'unreal-tesla-checks.json').write_text(json.dumps(report,indent=2))
state={'start':time.monotonic(),'task':None}
def tick(dt):
if time.monotonic()-state['start']<25:return
if state['task'] is None:
u.AutomationLibrary.finish_loading_before_screenshot();state['task']=u.AutomationLibrary.take_high_res_screenshot(1600,1000,str(P/'renders/unreal-tesla-garage.png'),camera=cam,delay=3)
elif state['task'].is_task_done():
u.unregister_slate_post_tick_callback(handle);u.log('TESLA_UNREAL_READY');u.SystemLibrary.quit_editor()
handle=u.register_slate_post_tick_callback(tick)

View File

@@ -0,0 +1,46 @@
import unreal as u,json,time,math,traceback
from pathlib import Path
ROOT=Path(__file__).resolve().parents[3];LEVEL=u.get_editor_subsystem(u.LevelEditorSubsystem);EDITOR=u.get_editor_subsystem(u.UnrealEditorSubsystem);report={'floor_traces':{},'movement':None};u.EditorPythonScripting.set_keep_python_script_alive(True)
state={'start':time.monotonic(),'phase':'load','t':0,'positions':[]}
def pos(v):return u.Vector(v[0]*100,-v[1]*100,v[2]*100)
def tick(dt):
try:
elapsed=time.monotonic()-state['start']
if state['phase']=='finish':
if time.monotonic()-state['t']>3:
u.unregister_slate_post_tick_callback(handle);u.log('HOMESTEAD_WALK_VERIFIED '+json.dumps(report['movement']));u.SystemLibrary.quit_editor()
return
if elapsed>90:raise RuntimeError('Play-in-editor movement test timed out')
if state['phase']=='load':
if elapsed<12:return
u.AutomationLibrary.finish_loading_before_screenshot();LEVEL.editor_request_begin_play();state['phase']='settle';state['t']=time.monotonic();return
game=EDITOR.get_game_world()
if not game:return
pawn=u.GameplayStatics.get_player_pawn(game,0)
if not pawn:return
if state['phase']=='settle':
if time.monotonic()-state['t']<4:return
assert 'HomesteadWalker' in pawn.get_class().get_name(),pawn.get_class().get_name()
for name,xy in [('verandah',(0,4)),('living',(-1.8,1)),('corridor',(-7.5,-1.8)),('bedroom',(-10.7,-3.5)),('meditation',(-11.8,10.8)),('garage',(2,-18)),('garden',(0,13)),('training',(-10,-19))]:
result=u.SystemLibrary.line_trace_single(game,pos((*xy,2.9)),pos((*xy,-2)),u.TraceTypeQuery.TRACE_TYPE_QUERY1,True,[pawn],u.DrawDebugTrace.NONE,True);assert result is not None,name;report['floor_traces'][name]='hit'
p=pawn.get_actor_location();state['initial']=[p.x,p.y,p.z];assert 170<p.z<240,('Spawn not on floor',p.z);state['phase']='car_settle';state['t']=time.monotonic();pawn.set_actor_location(u.Vector(-170,1500,153),False,True)
elif state['phase']=='car_settle':
if time.monotonic()-state['t']<1:return
state['phase']='car_walk';state['t']=time.monotonic()
elif state['phase']=='car_walk':
pawn.add_movement_input(u.Vector(0,1,0),1)
if time.monotonic()-state['t']>2.5:
q=pawn.get_actor_location();assert 1570<q.y<1680,('Car did not stop walking capsule',q)
report['tesla_collision']={'walk_stopped_before_car':True,'position_cm':[q.x,q.y,q.z]}
pawn.set_actor_location(u.Vector(*state['initial']),False,True);state['phase']='walk_reset';state['t']=time.monotonic()
elif state['phase']=='walk_reset':
if time.monotonic()-state['t']<1:return
state['phase']='walk';state['t']=time.monotonic()
elif state['phase']=='walk':
p=pawn.get_actor_location();state['positions'].append([p.x,p.y,p.z]);pawn.add_movement_input(u.Vector(0,1,0),1)
if time.monotonic()-state['t']>2.5:
a=state['initial'];dist=math.hypot(p.x-a[0],p.y-a[1]);assert dist>100,('No meaningful movement',dist);assert 170<p.z<240,('Pawn left walking floor',p.z)
report['movement']={'pawn':pawn.get_class().get_name(),'start_cm':a,'end_cm':[p.x,p.y,p.z],'distance_cm':dist,'movement_mode':str(pawn.character_movement.movement_mode),'controller_possessed':pawn.get_controller() is not None,'sampled_positions':len(state['positions'])};report['status']='PASS: daylight level, Tesla blocks walking, eight floor traces and possessed character walking from verandah into living entry';(ROOT/'unreal-walkthrough-verification.json').write_text(json.dumps(report,indent=2));LEVEL.editor_request_end_play();state['phase']='finish';state['t']=time.monotonic()
except Exception:
report['status']='FAILED';report['error']=traceback.format_exc();(ROOT/'unreal-walkthrough-verification.json').write_text(json.dumps(report,indent=2));u.log_error(report['error']);u.unregister_slate_post_tick_callback(handle);LEVEL.editor_request_end_play();u.SystemLibrary.quit_editor()
handle=u.register_slate_post_tick_callback(tick)

View File

@@ -0,0 +1,39 @@
{
"floor_traces": {
"verandah": "hit",
"living": "hit",
"corridor": "hit",
"bedroom": "hit",
"meditation": "hit",
"garage": "hit",
"garden": "hit",
"training": "hit"
},
"movement": {
"pawn": "BP_HomesteadWalker_C",
"start_cm": [
0.0,
-455.0,
208.91514610554694
],
"end_cm": [
0.0,
-109.60077457824171,
204.1499967251696
],
"distance_cm": 345.3992254217583,
"movement_mode": "<MovementMode.MOVE_WALKING: 1>",
"controller_possessed": true,
"sampled_positions": 59
},
"tesla_collision": {
"walk_stopped_before_car": true,
"position_cm": [
-170.0,
1651.1851263218496,
154.14999469910472
]
},
"status": "PASS: Tesla blocks walking, eight floor traces and possessed character walking from verandah into living entry",
"verification_scope": "Play-in-editor checks preceded final lighting-only corrections and removal of the non-colliding opaque haze box; fresh final daylight screenshots are recorded separately."
}

View File

@@ -0,0 +1,9 @@
import bpy,json
from pathlib import Path
P=Path(__file__).resolve().parents[1]
def snap():
c=bpy.data.collections['22 PLAY - guided camera tour'];s=bpy.context.scene
return {'captions':{o.name:o.data.body for o in c.objects if o.type=='FONT'},'frame_range':[s.frame_start,s.frame_end],'camera':s.camera.name,'markers':[(m.name,m.frame) for m in s.timeline_markers]}
bpy.ops.wm.open_mainfile(filepath=str(P/'base/samford-solarpunk-presentation-tour.blend'),use_scripts=False);a=snap()
bpy.ops.wm.open_mainfile(filepath=str(P/'samford-solarpunk-tesla-v008.blend'),use_scripts=False);b=snap();assert a==b,('Tour changed',a,b)
p=P/'blender-final-verification.json';r=json.loads(p.read_text());r['tour_chapters']=len(b['captions']);r['tour_matches_v007_exactly']=True;r['saved_file_reopened']=True;p.write_text(json.dumps(r,indent=2));print('SAVED_BLEND_VERIFIED',len(b['captions']))