Document licensed Tesla revision and daylight Unreal walkthrough
This commit is contained in:
8
source/samford-v008-tesla/asset-source.json
Normal file
8
source/samford-v008-tesla/asset-source.json
Normal 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/"
|
||||
}
|
||||
18
source/samford-v008-tesla/blender-final-verification.json
Normal file
18
source/samford-v008-tesla/blender-final-verification.json
Normal 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
|
||||
}
|
||||
40
source/samford-v008-tesla/export_tesla.py
Normal file
40
source/samford-v008-tesla/export_tesla.py
Normal 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)
|
||||
23
source/samford-v008-tesla/finalize_blender.py
Normal file
23
source/samford-v008-tesla/finalize_blender.py
Normal 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)
|
||||
72
source/samford-v008-tesla/replace_tesla.py
Normal file
72
source/samford-v008-tesla/replace_tesla.py
Normal 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)
|
||||
107
source/samford-v008-tesla/unreal-daylight-checks.json
Normal file
107
source/samford-v008-tesla/unreal-daylight-checks.json
Normal 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."
|
||||
}
|
||||
44
source/samford-v008-tesla/unreal-daylight.py
Normal file
44
source/samford-v008-tesla/unreal-daylight.py
Normal 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)
|
||||
24
source/samford-v008-tesla/unreal-fix_sun_direction.py
Normal file
24
source/samford-v008-tesla/unreal-fix_sun_direction.py
Normal 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)
|
||||
24
source/samford-v008-tesla/unreal-remove_opaque_haze.py
Normal file
24
source/samford-v008-tesla/unreal-remove_opaque_haze.py
Normal 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)
|
||||
44
source/samford-v008-tesla/unreal-replace_tesla.py
Normal file
44
source/samford-v008-tesla/unreal-replace_tesla.py
Normal 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)
|
||||
46
source/samford-v008-tesla/unreal-verify_walkthrough.py
Normal file
46
source/samford-v008-tesla/unreal-verify_walkthrough.py
Normal 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)
|
||||
@@ -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."
|
||||
}
|
||||
9
source/samford-v008-tesla/verify_saved_blender.py
Normal file
9
source/samford-v008-tesla/verify_saved_blender.py
Normal 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']))
|
||||
Reference in New Issue
Block a user