Files
samford-valley-home/source/samford-retreat-garage/export_meditation_unreal.py

72 lines
6.3 KiB
Python

import bpy,os,re,json,bmesh
from mathutils import Vector,Matrix
OUT=os.path.abspath(os.path.join(os.path.dirname(__file__),'../samford-meditation-unreal'));os.makedirs(OUT+'/Textures',exist_ok=True)
source=bpy.context.scene
detail=bpy.data.collections.get('DETAIL - full geometry for renders')
if detail:detail.hide_viewport=False
source.frame_set(2220);bpy.context.view_layer.update()
origin=Vector((-11,12,1.12))
objects=list(bpy.data.collections['23 NOW - Quiet meditation room'].objects)
objects += [o for o in bpy.data.collections['20 Spacious site connections'].objects if o.name.startswith('Meditation room')]
objects=[o for o in objects if o.type=='MESH' and not o.name.startswith(('Meditation covered walk','Meditation walk'))]
deps=bpy.context.evaluated_depsgraph_get()
new=bpy.data.scenes.new('Meditation room - Unreal transfer');new.unit_settings.system='METRIC';new.unit_settings.scale_length=1
manifest={'source':'Samford solarpunk homestead','origin_blender_metres':list(origin),'room_metres':[4.5,4.8],'objects':[],'materials':{},'camera_blender_metres':[1.7,2.0,1.88],'target_blender_metres':[-.5,-.5,.43],'camera_lens_mm':25,'render_status':'Prepared for Unreal; no Unreal render has been produced by this exporter.'}
new_objects=[];used=[]
for i,o in enumerate(objects):
ev=o.evaluated_get(deps);me=bpy.data.meshes.new_from_object(ev,preserve_all_data_layers=True,depsgraph=deps)
me.transform(Matrix.Translation(-origin)@o.matrix_world)
ob=bpy.data.objects.new('SM_%03d_'%i+re.sub('[^a-zA-Z0-9_]','_',o.name),me);new.collection.objects.link(ob);new_objects.append(ob)
for mat in me.materials:
if mat and mat not in used:used.append(mat)
manifest['objects'].append({'name':ob.name,'source':o.name})
bpy.context.window.scene=new
# Unwrap the small room assets; source procedural shaders remain in the original file.
for o in new_objects:
bpy.ops.object.select_all(action='DESELECT');o.select_set(True);bpy.context.view_layer.objects.active=o
bpy.ops.object.mode_set(mode='EDIT');bpy.ops.mesh.select_all(action='SELECT');bpy.ops.uv.smart_project(island_margin=.025);bpy.ops.object.mode_set(mode='OBJECT')
new.render.engine='CYCLES';new.cycles.device='CPU';new.cycles.samples=1;new.render.bake.margin=4
bpy.ops.object.select_all(action='DESELECT');bpy.ops.mesh.primitive_plane_add(size=2);tile=bpy.context.object;tile.name='Temporary material baking tile'
replacement={}
for i,m in enumerate(used):
key='M_%02d_'%i+re.sub('[^a-zA-Z0-9_]','_',m.name)
p=next((n for n in m.node_tree.nodes if n.type=='BSDF_PRINCIPLED'),None) if m.use_nodes else None
base=list(m.diffuse_color);rough=float(p.inputs['Roughness'].default_value) if p else .7;metal=float(p.inputs['Metallic'].default_value) if p else 0
record={'source':m.name,'base_color':base,'roughness':rough,'metallic':metal,'emission':0,'transmission':0}
if p:
record['emission']=float(p.inputs['Emission Strength'].default_value)
record['transmission']=float(p.inputs['Transmission Weight'].default_value)
exported=bpy.data.materials.new(key);exported.use_nodes=True;ep=exported.node_tree.nodes.get('Principled BSDF');ep.inputs['Base Color'].default_value=base;ep.inputs['Roughness'].default_value=rough;ep.inputs['Metallic'].default_value=metal
if p and p.inputs['Base Color'].is_linked:
bake=m.copy();tile.data.materials.clear();tile.data.materials.append(bake);nodes=bake.node_tree.nodes;links=bake.node_tree.links;bp=next(n for n in nodes if n.type=='BSDF_PRINCIPLED');output=next(n for n in nodes if n.type=='OUTPUT_MATERIAL')
im=bpy.data.images.new(key+'_BaseColor',width=512,height=512);im.filepath_raw=OUT+'/Textures/'+key+'_BaseColor.png';im.file_format='PNG'
target=nodes.new('ShaderNodeTexImage');target.image=im;nodes.active=target
emission=nodes.new('ShaderNodeEmission');links.new(bp.inputs['Base Color'].links[0].from_socket,emission.inputs['Color']);links.new(emission.outputs[0],output.inputs['Surface'])
bpy.ops.object.select_all(action='DESELECT');tile.select_set(True);bpy.context.view_layer.objects.active=tile;bpy.ops.object.bake(type='EMIT');im.save()
tex=exported.node_tree.nodes.new('ShaderNodeTexImage');tex.image=im;exported.node_tree.links.new(tex.outputs['Color'],ep.inputs['Base Color']);record['base_color_texture']='Textures/'+key+'_BaseColor.png'
if bp.inputs['Normal'].is_linked:
normal_image=bpy.data.images.new(key+'_Normal',width=512,height=512)
normal_image.colorspace_settings.name='Non-Color';normal_image.filepath_raw=OUT+'/Textures/'+key+'_Normal.png';normal_image.file_format='PNG'
target.image=normal_image;nodes.active=target;links.new(bp.outputs['BSDF'],output.inputs['Surface'])
bpy.ops.object.bake(type='NORMAL');normal_image.save()
normal_tex=exported.node_tree.nodes.new('ShaderNodeTexImage');normal_tex.image=normal_image
normal_node=exported.node_tree.nodes.new('ShaderNodeNormalMap');exported.node_tree.links.new(normal_tex.outputs['Color'],normal_node.inputs['Color']);exported.node_tree.links.new(normal_node.outputs['Normal'],ep.inputs['Normal'])
record['normal_texture']='Textures/'+key+'_Normal.png'
record['normal_convention']='OpenGL +Y'
bpy.data.materials.remove(bake)
replacement[m]=exported;manifest['materials'][key]=record
print('MATERIAL_READY '+key,flush=True)
for o in new_objects:
for slot in o.material_slots:
if slot.material in replacement:slot.material=replacement[slot.material]
bpy.data.objects.remove(tile,do_unlink=True)
# This deliberately keeps the viewing face open for the simple room render.
bpy.ops.object.select_all(action='DESELECT')
for o in new_objects:o.select_set(True)
bpy.context.view_layer.objects.active=new_objects[0]
bpy.ops.export_scene.fbx(filepath=OUT+'/MeditationRoom.fbx',use_selection=True,object_types={'MESH'},use_mesh_modifiers=False,add_leaf_bones=False,bake_anim=False,axis_forward='-Y',axis_up='Z',path_mode='RELATIVE')
bpy.ops.export_scene.gltf(filepath=OUT+'/MeditationRoom.glb',export_format='GLB',use_selection=True,export_animations=False)
json.dump(manifest,open(OUT+'/meditation-room-manifest.json','w'),indent=2)
bpy.ops.wm.save_as_mainfile(filepath=OUT+'/meditation-room-transfer.blend')
print('MEDITATION_EXPORT_READY '+str(len(new_objects))+' meshes, '+str(len(used))+' materials',flush=True)