72 lines
6.3 KiB
Python
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)
|