678 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			678 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|  | import bpy.ops as O | ||
|  | import bpy, os, re, sys, importlib, struct, platform, subprocess, threading, string, bmesh, shutil, glob, uuid | ||
|  | from io import StringIO | ||
|  | from threading  import Thread | ||
|  | from queue import Queue, Empty | ||
|  | from dataclasses import dataclass | ||
|  | from dataclasses import field | ||
|  | from typing import List | ||
|  | 
 | ||
|  | ########################################################### | ||
|  | ########################################################### | ||
|  | # This set of utility functions are courtesy of LorenzWieseke | ||
|  | # | ||
|  | # Modified by Naxela | ||
|  | #    | ||
|  | # https://github.com/Naxela/The_Lightmapper/tree/Lightmap-to-GLB | ||
|  | ########################################################### | ||
|  | 
 | ||
|  | class Node_Types: | ||
|  |     output_node = 'OUTPUT_MATERIAL' | ||
|  |     ao_node = 'AMBIENT_OCCLUSION' | ||
|  |     image_texture = 'TEX_IMAGE' | ||
|  |     pbr_node = 'BSDF_PRINCIPLED' | ||
|  |     diffuse = 'BSDF_DIFFUSE' | ||
|  |     mapping = 'MAPPING' | ||
|  |     normal_map = 'NORMAL_MAP' | ||
|  |     bump_map = 'BUMP' | ||
|  |     attr_node = 'ATTRIBUTE' | ||
|  | 
 | ||
|  | class Shader_Node_Types: | ||
|  |     emission = "ShaderNodeEmission" | ||
|  |     image_texture = "ShaderNodeTexImage" | ||
|  |     mapping = "ShaderNodeMapping" | ||
|  |     normal = "ShaderNodeNormalMap" | ||
|  |     ao = "ShaderNodeAmbientOcclusion" | ||
|  |     uv = "ShaderNodeUVMap" | ||
|  |     mix = "ShaderNodeMixRGB" | ||
|  | 
 | ||
|  | def select_object(self,obj): | ||
|  |     C = bpy.context | ||
|  |     try: | ||
|  |         O.object.select_all(action='DESELECT') | ||
|  |         C.view_layer.objects.active = obj | ||
|  |         obj.select_set(True) | ||
|  |     except: | ||
|  |         self.report({'INFO'},"Object not in View Layer") | ||
|  | 
 | ||
|  | 
 | ||
|  | def select_obj_by_mat(self,mat): | ||
|  |     D = bpy.data | ||
|  |     for obj in D.objects: | ||
|  |         if obj.type == "MESH": | ||
|  |             object_materials = [ | ||
|  |                 slot.material for slot in obj.material_slots] | ||
|  |             if mat in object_materials: | ||
|  |                 select_object(self,obj) | ||
|  | 
 | ||
|  | 
 | ||
|  | def save_image(image): | ||
|  | 
 | ||
|  |     filePath = bpy.data.filepath | ||
|  |     path = os.path.dirname(filePath) | ||
|  | 
 | ||
|  |     try: | ||
|  |         os.mkdir(path + "/tex") | ||
|  |     except FileExistsError: | ||
|  |         pass | ||
|  | 
 | ||
|  |     try: | ||
|  |         os.mkdir(path + "/tex/" + str(image.size[0])) | ||
|  |     except FileExistsError: | ||
|  |         pass | ||
|  | 
 | ||
|  |     if image.file_format == "JPEG" : | ||
|  |         file_ending = ".jpg" | ||
|  |     elif image.file_format == "PNG" : | ||
|  |         file_ending = ".png" | ||
|  |      | ||
|  |     savepath = path + "/tex/" + \ | ||
|  |         str(image.size[0]) + "/" + image.name + file_ending | ||
|  | 
 | ||
|  |     image.filepath_raw = savepath | ||
|  |      | ||
|  |     image.save() | ||
|  | 
 | ||
|  | def get_file_size(filepath): | ||
|  |     size = "Unpack Files" | ||
|  |     try: | ||
|  |         path = bpy.path.abspath(filepath) | ||
|  |         size = os.path.getsize(path) | ||
|  |         size /= 1024 | ||
|  |     except: | ||
|  |         if bpy.context.scene.TLM_SceneProperties.tlm_verbose: | ||
|  |             print("error getting file path for " + filepath) | ||
|  |          | ||
|  |     return (size) | ||
|  | 
 | ||
|  | 
 | ||
|  | def scale_image(image, newSize): | ||
|  |     if (image.org_filepath != ''): | ||
|  |         image.filepath = image.org_filepath | ||
|  | 
 | ||
|  |     image.org_filepath = image.filepath | ||
|  |     image.scale(newSize[0], newSize[1]) | ||
|  |     save_image(image) | ||
|  | 
 | ||
|  | 
 | ||
|  | def check_only_one_pbr(self,material): | ||
|  |     check_ok = True | ||
|  |     # get pbr shader | ||
|  |     nodes = material.node_tree.nodes | ||
|  |     pbr_node_type = Node_Types.pbr_node | ||
|  |     pbr_nodes = find_node_by_type(nodes,pbr_node_type) | ||
|  | 
 | ||
|  |     # check only one pbr node | ||
|  |     if len(pbr_nodes) == 0: | ||
|  |         self.report({'INFO'}, 'No PBR Shader Found') | ||
|  |         check_ok = False | ||
|  | 
 | ||
|  |     if len(pbr_nodes) > 1: | ||
|  |         self.report({'INFO'}, 'More than one PBR Node found ! Clean before Baking.') | ||
|  |         check_ok = False | ||
|  | 
 | ||
|  |     return check_ok | ||
|  | 
 | ||
|  | # is material already the baked one | ||
|  | def check_is_org_material(self,material):      | ||
|  |     check_ok = True    | ||
|  |     if "_Bake" in material.name: | ||
|  |         self.report({'INFO'}, 'Change back to org. Material') | ||
|  |         check_ok = False | ||
|  |      | ||
|  |     return check_ok | ||
|  | 
 | ||
|  | 
 | ||
|  | def clean_empty_materials(self): | ||
|  |     for obj in bpy.context.scene.objects: | ||
|  |         for slot in obj.material_slots: | ||
|  |             mat = slot.material | ||
|  |             if mat is None: | ||
|  |                 if bpy.context.scene.TLM_SceneProperties.tlm_verbose: | ||
|  |                     print("Removed Empty Materials from " + obj.name) | ||
|  |                 bpy.ops.object.select_all(action='DESELECT') | ||
|  |                 obj.select_set(True) | ||
|  |                 bpy.ops.object.material_slot_remove() | ||
|  | 
 | ||
|  | def get_pbr_inputs(pbr_node): | ||
|  | 
 | ||
|  |     base_color_input = pbr_node.inputs["Base Color"] | ||
|  |     metallic_input = pbr_node.inputs["Metallic"] | ||
|  |     specular_input = pbr_node.inputs["Specular"] | ||
|  |     roughness_input = pbr_node.inputs["Roughness"] | ||
|  |     normal_input = pbr_node.inputs["Normal"] | ||
|  | 
 | ||
|  |     pbr_inputs = {"base_color_input":base_color_input, "metallic_input":metallic_input,"specular_input":specular_input,"roughness_input":roughness_input,"normal_input":normal_input} | ||
|  |     return pbr_inputs     | ||
|  | 
 | ||
|  | def find_node_by_type(nodes, node_type): | ||
|  |     nodes_found = [n for n in nodes if n.type == node_type] | ||
|  |     return nodes_found | ||
|  | 
 | ||
|  | def find_node_by_type_recusivly(material, note_to_start, node_type, del_nodes_inbetween=False): | ||
|  |     nodes = material.node_tree.nodes | ||
|  |     if note_to_start.type == node_type: | ||
|  |         return note_to_start | ||
|  | 
 | ||
|  |     for input in note_to_start.inputs: | ||
|  |         for link in input.links: | ||
|  |             current_node = link.from_node | ||
|  |             if (del_nodes_inbetween and note_to_start.type != Node_Types.normal_map and note_to_start.type != Node_Types.bump_map): | ||
|  |                 nodes.remove(note_to_start) | ||
|  |             return find_node_by_type_recusivly(material, current_node, node_type, del_nodes_inbetween) | ||
|  | 
 | ||
|  | 
 | ||
|  | def find_node_by_name_recusivly(node, idname): | ||
|  |     if node.bl_idname == idname: | ||
|  |         return node | ||
|  | 
 | ||
|  |     for input in node.inputs: | ||
|  |         for link in input.links: | ||
|  |             current_node = link.from_node | ||
|  |             return find_node_by_name_recusivly(current_node, idname) | ||
|  | 
 | ||
|  | def make_link(material, socket1, socket2): | ||
|  |     links = material.node_tree.links | ||
|  |     links.new(socket1, socket2) | ||
|  | 
 | ||
|  | 
 | ||
|  | def add_gamma_node(material, pbrInput): | ||
|  |     nodeToPrincipledOutput = pbrInput.links[0].from_socket | ||
|  | 
 | ||
|  |     gammaNode = material.node_tree.nodes.new("ShaderNodeGamma") | ||
|  |     gammaNode.inputs[1].default_value = 2.2 | ||
|  |     gammaNode.name = "Gamma Bake" | ||
|  | 
 | ||
|  |     # link in gamma | ||
|  |     make_link(material, nodeToPrincipledOutput, gammaNode.inputs["Color"]) | ||
|  |     make_link(material, gammaNode.outputs["Color"], pbrInput) | ||
|  | 
 | ||
|  | 
 | ||
|  | def remove_gamma_node(material, pbrInput): | ||
|  |     nodes = material.node_tree.nodes | ||
|  |     gammaNode = nodes.get("Gamma Bake") | ||
|  |     nodeToPrincipledOutput = gammaNode.inputs[0].links[0].from_socket | ||
|  | 
 | ||
|  |     make_link(material, nodeToPrincipledOutput, pbrInput) | ||
|  |     material.node_tree.nodes.remove(gammaNode) | ||
|  | 
 | ||
|  | def apply_ao_toggle(self,context):  | ||
|  |     all_materials = bpy.data.materials | ||
|  |     ao_toggle = context.scene.toggle_ao | ||
|  |     for mat in all_materials: | ||
|  |         nodes = mat.node_tree.nodes | ||
|  |         ao_node = nodes.get("AO Bake") | ||
|  |         if ao_node is not None: | ||
|  |             if ao_toggle: | ||
|  |                 emission_setup(mat,ao_node.outputs["Color"]) | ||
|  |             else: | ||
|  |                 pbr_node = find_node_by_type(nodes,Node_Types.pbr_node)[0]    | ||
|  |                 remove_node(mat,"Emission Bake") | ||
|  |                 reconnect_PBR(mat, pbr_node) | ||
|  |          | ||
|  | 
 | ||
|  | def emission_setup(material, node_output): | ||
|  |     nodes = material.node_tree.nodes | ||
|  |     emission_node = add_node(material,Shader_Node_Types.emission,"Emission Bake") | ||
|  | 
 | ||
|  |     # link emission to whatever goes into current pbrInput | ||
|  |     emission_input = emission_node.inputs[0] | ||
|  |     make_link(material, node_output, emission_input) | ||
|  | 
 | ||
|  |     # link emission to materialOutput | ||
|  |     surface_input = nodes.get("Material Output").inputs[0] | ||
|  |     emission_output = emission_node.outputs[0] | ||
|  |     make_link(material, emission_output, surface_input) | ||
|  | 
 | ||
|  | def link_pbr_to_output(material,pbr_node): | ||
|  |     nodes = material.node_tree.nodes | ||
|  |     surface_input = nodes.get("Material Output").inputs[0] | ||
|  |     make_link(material,pbr_node.outputs[0],surface_input) | ||
|  | 
 | ||
|  |      | ||
|  | def reconnect_PBR(material, pbrNode): | ||
|  |     nodes = material.node_tree.nodes | ||
|  |     pbr_output = pbrNode.outputs[0] | ||
|  |     surface_input = nodes.get("Material Output").inputs[0] | ||
|  |     make_link(material, pbr_output, surface_input) | ||
|  | 
 | ||
|  | def mute_all_texture_mappings(material, do_mute): | ||
|  |     nodes = material.node_tree.nodes | ||
|  |     for node in nodes: | ||
|  |         if node.bl_idname == "ShaderNodeMapping": | ||
|  |             node.mute = do_mute | ||
|  | 
 | ||
|  | def add_node(material,shader_node_type,node_name): | ||
|  |     nodes = material.node_tree.nodes | ||
|  |     new_node = nodes.get(node_name) | ||
|  |     if new_node is None: | ||
|  |         new_node = nodes.new(shader_node_type) | ||
|  |         new_node.name = node_name | ||
|  |         new_node.label = node_name | ||
|  |     return new_node | ||
|  | 
 | ||
|  | def remove_node(material,node_name): | ||
|  |     nodes = material.node_tree.nodes | ||
|  |     node = nodes.get(node_name) | ||
|  |     if node is not None: | ||
|  |         nodes.remove(node) | ||
|  | 
 | ||
|  | def lightmap_to_ao(material,lightmap_node): | ||
|  |         nodes = material.node_tree.nodes | ||
|  |         # -----------------------AO SETUP--------------------# | ||
|  |         # create group data | ||
|  |         gltf_settings = bpy.data.node_groups.get('glTF Settings') | ||
|  |         if gltf_settings is None: | ||
|  |             bpy.data.node_groups.new('glTF Settings', 'ShaderNodeTree') | ||
|  |          | ||
|  |         # add group to node tree | ||
|  |         ao_group = nodes.get('glTF Settings') | ||
|  |         if ao_group is None: | ||
|  |             ao_group = nodes.new('ShaderNodeGroup') | ||
|  |             ao_group.name = 'glTF Settings' | ||
|  |             ao_group.node_tree = bpy.data.node_groups['glTF Settings'] | ||
|  | 
 | ||
|  |         # create group inputs | ||
|  |         if ao_group.inputs.get('Occlusion') is None: | ||
|  |             ao_group.inputs.new('NodeSocketFloat','Occlusion') | ||
|  | 
 | ||
|  |         # mulitply to control strength | ||
|  |         mix_node = add_node(material,Shader_Node_Types.mix,"Adjust Lightmap") | ||
|  |         mix_node.blend_type = "MULTIPLY" | ||
|  |         mix_node.inputs["Fac"].default_value = 1 | ||
|  |         mix_node.inputs["Color2"].default_value = [3,3,3,1] | ||
|  | 
 | ||
|  |         # position node | ||
|  |         ao_group.location = (lightmap_node.location[0]+600,lightmap_node.location[1]) | ||
|  |         mix_node.location = (lightmap_node.location[0]+300,lightmap_node.location[1]) | ||
|  |      | ||
|  |         make_link(material,lightmap_node.outputs['Color'],mix_node.inputs['Color1']) | ||
|  |         make_link(material,mix_node.outputs['Color'],ao_group.inputs['Occlusion']) | ||
|  | 
 | ||
|  | 
 | ||
|  | ########################################################### | ||
|  | ########################################################### | ||
|  | # This utility function is modified from blender_xatlas | ||
|  | # and calls the object without any explicit object context | ||
|  | # thus allowing blender_xatlas to pack from background. | ||
|  | ########################################################### | ||
|  | # Code is courtesy of mattedicksoncom | ||
|  | # Modified by Naxela | ||
|  | # | ||
|  | # https://github.com/mattedicksoncom/blender-xatlas/ | ||
|  | ########################################################### | ||
|  | 
 | ||
|  | def gen_safe_name(): | ||
|  |     genId = uuid.uuid4().hex | ||
|  |     # genId = "u_" + genId.replace("-","_") | ||
|  |     return "u_" + genId | ||
|  | 
 | ||
|  | def Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj): | ||
|  | 
 | ||
|  |     blender_xatlas = importlib.util.find_spec("blender_xatlas") | ||
|  | 
 | ||
|  |     if blender_xatlas is not None: | ||
|  |         import blender_xatlas | ||
|  |     else: | ||
|  |         return 0 | ||
|  | 
 | ||
|  |     packOptions = bpy.context.scene.pack_tool | ||
|  |     chartOptions = bpy.context.scene.chart_tool | ||
|  | 
 | ||
|  |     sharedProperties = bpy.context.scene.shared_properties | ||
|  |     #sharedProperties.unwrapSelection | ||
|  | 
 | ||
|  |     context = bpy.context | ||
|  | 
 | ||
|  |     #save whatever mode the user was in | ||
|  |     startingMode = bpy.context.object.mode | ||
|  |     selected_objects = bpy.context.selected_objects | ||
|  | 
 | ||
|  |     #check something is actually selected | ||
|  |     #external function/operator will select them | ||
|  |     if len(selected_objects) == 0: | ||
|  |         print("Nothing Selected") | ||
|  |         self.report({"WARNING"}, "Nothing Selected, please select Something") | ||
|  |         return {'FINISHED'} | ||
|  | 
 | ||
|  |     #store the names of objects to be lightmapped | ||
|  |     rename_dict = dict() | ||
|  |     safe_dict = dict() | ||
|  | 
 | ||
|  |     #make sure all the objects have ligthmap uvs | ||
|  |     for obj in selected_objects: | ||
|  |         if obj.type == 'MESH': | ||
|  |             safe_name = gen_safe_name(); | ||
|  |             rename_dict[obj.name] = (obj.name,safe_name) | ||
|  |             safe_dict[safe_name] = obj.name | ||
|  |             context.view_layer.objects.active = obj | ||
|  |             if obj.data.users > 1: | ||
|  |                 obj.data = obj.data.copy() #make single user copy | ||
|  |             uv_layers = obj.data.uv_layers | ||
|  | 
 | ||
|  |             #setup the lightmap uvs | ||
|  |             uvName = "UVMap_Lightmap" | ||
|  |             if sharedProperties.lightmapUVChoiceType == "NAME": | ||
|  |                 uvName = sharedProperties.lightmapUVName | ||
|  |             elif sharedProperties.lightmapUVChoiceType == "INDEX": | ||
|  |                 if sharedProperties.lightmapUVIndex < len(uv_layers): | ||
|  |                     uvName = uv_layers[sharedProperties.lightmapUVIndex].name | ||
|  | 
 | ||
|  |             if not uvName in uv_layers: | ||
|  |                 uvmap = uv_layers.new(name=uvName) | ||
|  |                 uv_layers.active_index = len(uv_layers) - 1 | ||
|  |             else: | ||
|  |                 for i in range(0, len(uv_layers)): | ||
|  |                     if uv_layers[i].name == uvName: | ||
|  |                         uv_layers.active_index = i | ||
|  |             obj.select_set(True) | ||
|  | 
 | ||
|  |     #save all the current edges | ||
|  |     if sharedProperties.packOnly: | ||
|  |         edgeDict = dict() | ||
|  |         for obj in selected_objects: | ||
|  |             if obj.type == 'MESH': | ||
|  |                 tempEdgeDict = dict() | ||
|  |                 tempEdgeDict['object'] = obj.name | ||
|  |                 tempEdgeDict['edges'] = [] | ||
|  |                 print(len(obj.data.edges)) | ||
|  |                 for i in range(0,len(obj.data.edges)): | ||
|  |                     setEdge = obj.data.edges[i] | ||
|  |                     tempEdgeDict['edges'].append(i) | ||
|  |                 edgeDict[obj.name] = tempEdgeDict | ||
|  | 
 | ||
|  |         bpy.ops.object.mode_set(mode='EDIT') | ||
|  |         bpy.ops.mesh.select_all(action='SELECT') | ||
|  |         bpy.ops.mesh.quads_convert_to_tris(quad_method='FIXED', ngon_method='BEAUTY') | ||
|  |     else: | ||
|  |         bpy.ops.object.mode_set(mode='EDIT') | ||
|  |         bpy.ops.mesh.select_all(action='SELECT') | ||
|  |         bpy.ops.mesh.quads_convert_to_tris(quad_method='FIXED', ngon_method='BEAUTY') | ||
|  | 
 | ||
|  |     bpy.ops.object.mode_set(mode='OBJECT') | ||
|  | 
 | ||
|  |     #Create a fake obj export to a string | ||
|  |     #Will strip this down further later | ||
|  |     fakeFile = StringIO() | ||
|  |     blender_xatlas.export_obj_simple.save( | ||
|  |         rename_dict=rename_dict, | ||
|  |         context=bpy.context, | ||
|  |         filepath=fakeFile, | ||
|  |         mainUVChoiceType=sharedProperties.mainUVChoiceType, | ||
|  |         uvIndex=sharedProperties.mainUVIndex, | ||
|  |         uvName=sharedProperties.mainUVName, | ||
|  |         use_selection=True, | ||
|  |         use_animation=False, | ||
|  |         use_mesh_modifiers=True, | ||
|  |         use_edges=True, | ||
|  |         use_smooth_groups=False, | ||
|  |         use_smooth_groups_bitflags=False, | ||
|  |         use_normals=True, | ||
|  |         use_uvs=True, | ||
|  |         use_materials=False, | ||
|  |         use_triangles=False, | ||
|  |         use_nurbs=False, | ||
|  |         use_vertex_groups=False, | ||
|  |         use_blen_objects=True, | ||
|  |         group_by_object=False, | ||
|  |         group_by_material=False, | ||
|  |         keep_vertex_order=False, | ||
|  |     ) | ||
|  | 
 | ||
|  |     #print just for reference | ||
|  |     # print(fakeFile.getvalue()) | ||
|  | 
 | ||
|  |     #get the path to xatlas | ||
|  |     #file_path = os.path.dirname(os.path.abspath(__file__)) | ||
|  |     scriptsDir = os.path.join(bpy.utils.user_resource('SCRIPTS'), "addons") | ||
|  |     file_path = os.path.join(scriptsDir, "blender_xatlas") | ||
|  |     if platform.system() == "Windows": | ||
|  |         xatlas_path = os.path.join(file_path, "xatlas", "xatlas-blender.exe") | ||
|  |     elif platform.system() == "Linux": | ||
|  |         xatlas_path = os.path.join(file_path, "xatlas", "xatlas-blender") | ||
|  |         #need to set permissions for the process on linux | ||
|  |         subprocess.Popen( | ||
|  |             'chmod u+x "' + xatlas_path + '"', | ||
|  |             shell=True | ||
|  |         ) | ||
|  | 
 | ||
|  |     #setup the arguments to be passed to xatlas------------------- | ||
|  |     arguments_string = "" | ||
|  |     for argumentKey in packOptions.__annotations__.keys(): | ||
|  |         key_string = str(argumentKey) | ||
|  |         if argumentKey is not None: | ||
|  |             print(getattr(packOptions,key_string)) | ||
|  |             attrib = getattr(packOptions,key_string) | ||
|  |             if type(attrib) == bool: | ||
|  |                 if attrib == True: | ||
|  |                     arguments_string = arguments_string + " -" + str(argumentKey) | ||
|  |             else: | ||
|  |                 arguments_string = arguments_string + " -" + str(argumentKey) + " " + str(attrib) | ||
|  | 
 | ||
|  |     for argumentKey in chartOptions.__annotations__.keys(): | ||
|  |         if argumentKey is not None: | ||
|  |             key_string = str(argumentKey) | ||
|  |             print(getattr(chartOptions,key_string)) | ||
|  |             attrib = getattr(chartOptions,key_string) | ||
|  |             if type(attrib) == bool: | ||
|  |                 if attrib == True: | ||
|  |                     arguments_string = arguments_string + " -" + str(argumentKey) | ||
|  |             else: | ||
|  |                 arguments_string = arguments_string + " -" + str(argumentKey) + " " + str(attrib) | ||
|  | 
 | ||
|  |     #add pack only option | ||
|  |     if sharedProperties.packOnly: | ||
|  |         arguments_string = arguments_string + " -packOnly" | ||
|  | 
 | ||
|  |     arguments_string = arguments_string + " -atlasLayout" + " " + sharedProperties.atlasLayout | ||
|  | 
 | ||
|  |     print(arguments_string) | ||
|  |     #END setup the arguments to be passed to xatlas------------------- | ||
|  | 
 | ||
|  |     #RUN xatlas process | ||
|  |     xatlas_process = subprocess.Popen( | ||
|  |         r'"{}"'.format(xatlas_path) + ' ' + arguments_string, | ||
|  |         stdin=subprocess.PIPE, | ||
|  |         stdout=subprocess.PIPE, | ||
|  |         shell=True | ||
|  |     ) | ||
|  | 
 | ||
|  |     print(xatlas_path) | ||
|  | 
 | ||
|  |     #shove the fake file in stdin | ||
|  |     stdin = xatlas_process.stdin | ||
|  |     value = bytes(fakeFile.getvalue() + "\n", 'UTF-8') #The \n is needed to end the input properly | ||
|  |     stdin.write(value) | ||
|  |     stdin.flush() | ||
|  | 
 | ||
|  |     #Get the output from xatlas | ||
|  |     outObj = "" | ||
|  |     while True: | ||
|  |         output = xatlas_process.stdout.readline() | ||
|  |         if not output: | ||
|  |                 break | ||
|  |         outObj = outObj + (output.decode().strip() + "\n") | ||
|  | 
 | ||
|  |     #the objects after xatlas processing | ||
|  |     # print(outObj) | ||
|  | 
 | ||
|  | 
 | ||
|  |     #Setup for reading the output | ||
|  |     @dataclass | ||
|  |     class uvObject: | ||
|  |         obName: string = "" | ||
|  |         uvArray: List[float] = field(default_factory=list) | ||
|  |         faceArray: List[int] = field(default_factory=list) | ||
|  | 
 | ||
|  |     convertedObjects = [] | ||
|  |     uvArrayComplete = [] | ||
|  | 
 | ||
|  | 
 | ||
|  |     #search through the out put for STARTOBJ | ||
|  |     #then start reading the objects | ||
|  |     obTest = None | ||
|  |     startRead = False | ||
|  |     for line in outObj.splitlines(): | ||
|  | 
 | ||
|  |         line_split = line.split() | ||
|  | 
 | ||
|  |         if not line_split: | ||
|  |             continue | ||
|  | 
 | ||
|  |         line_start = line_split[0]  # we compare with this a _lot_ | ||
|  |         # print(line_start) | ||
|  |         if line_start == "STARTOBJ": | ||
|  |             print("Start reading the objects----------------------------------------") | ||
|  |             startRead = True | ||
|  |             # obTest = uvObject() | ||
|  | 
 | ||
|  |         if startRead: | ||
|  |             #if it's a new obj | ||
|  |             if line_start == 'o': | ||
|  |                 #if there is already an object append it | ||
|  |                 if obTest is not None: | ||
|  |                     convertedObjects.append(obTest) | ||
|  | 
 | ||
|  |                 obTest = uvObject() #create new uv object | ||
|  |                 obTest.obName = line_split[1] | ||
|  | 
 | ||
|  |             if obTest is not None: | ||
|  |                 #the uv coords | ||
|  |                 if line_start == 'vt': | ||
|  |                     newUv = [float(line_split[1]),float(line_split[2])] | ||
|  |                     obTest.uvArray.append(newUv) | ||
|  |                     uvArrayComplete.append(newUv) | ||
|  | 
 | ||
|  |                 #the face coords index | ||
|  |                 #faces are 1 indexed | ||
|  |                 if line_start == 'f': | ||
|  |                     #vert/uv/normal | ||
|  |                     #only need the uvs | ||
|  |                     newFace = [ | ||
|  |                         int(line_split[1].split("/")[1]), | ||
|  |                         int(line_split[2].split("/")[1]), | ||
|  |                         int(line_split[3].split("/")[1]) | ||
|  |                     ] | ||
|  |                     obTest.faceArray.append(newFace) | ||
|  | 
 | ||
|  |     #append the final object | ||
|  |     convertedObjects.append(obTest) | ||
|  |     print(convertedObjects) | ||
|  | 
 | ||
|  | 
 | ||
|  |     #apply the output------------------------------------------------------------- | ||
|  |     #copy the uvs to the original objects | ||
|  |     # objIndex = 0 | ||
|  |     print("Applying the UVs----------------------------------------") | ||
|  |     # print(convertedObjects) | ||
|  |     for importObject in convertedObjects: | ||
|  |         bpy.ops.object.select_all(action='DESELECT') | ||
|  | 
 | ||
|  |         obTest = importObject | ||
|  |         obTest.obName = safe_dict[obTest.obName] #probably shouldn't just replace it | ||
|  |         bpy.context.scene.objects[obTest.obName].select_set(True) | ||
|  |         context.view_layer.objects.active = bpy.context.scene.objects[obTest.obName] | ||
|  |         bpy.ops.object.mode_set(mode = 'OBJECT') | ||
|  | 
 | ||
|  |         obj = bpy.context.active_object | ||
|  |         me = obj.data | ||
|  |         #convert to bmesh to create the new uvs | ||
|  |         bm = bmesh.new() | ||
|  |         bm.from_mesh(me) | ||
|  | 
 | ||
|  |         uv_layer = bm.loops.layers.uv.verify() | ||
|  | 
 | ||
|  |         nFaces = len(bm.faces) | ||
|  |         #need to ensure lookup table for some reason? | ||
|  |         if hasattr(bm.faces, "ensure_lookup_table"): | ||
|  |             bm.faces.ensure_lookup_table() | ||
|  | 
 | ||
|  |         #loop through the faces | ||
|  |         for faceIndex in range(nFaces): | ||
|  |             faceGroup = obTest.faceArray[faceIndex] | ||
|  | 
 | ||
|  |             bm.faces[faceIndex].loops[0][uv_layer].uv = ( | ||
|  |                 uvArrayComplete[faceGroup[0] - 1][0], | ||
|  |                 uvArrayComplete[faceGroup[0] - 1][1]) | ||
|  | 
 | ||
|  |             bm.faces[faceIndex].loops[1][uv_layer].uv = ( | ||
|  |                 uvArrayComplete[faceGroup[1] - 1][0], | ||
|  |                 uvArrayComplete[faceGroup[1] - 1][1]) | ||
|  | 
 | ||
|  |             bm.faces[faceIndex].loops[2][uv_layer].uv = ( | ||
|  |                 uvArrayComplete[faceGroup[2] - 1][0], | ||
|  |                 uvArrayComplete[faceGroup[2] - 1][1]) | ||
|  | 
 | ||
|  |             # objIndex = objIndex + 3 | ||
|  | 
 | ||
|  |         # print(objIndex) | ||
|  |         #assign the mesh back to the original mesh | ||
|  |         bm.to_mesh(me) | ||
|  |     #END apply the output------------------------------------------------------------- | ||
|  | 
 | ||
|  | 
 | ||
|  |     #Start setting the quads back again------------------------------------------------------------- | ||
|  |     if sharedProperties.packOnly: | ||
|  |         bpy.ops.object.mode_set(mode='EDIT') | ||
|  |         bpy.ops.mesh.select_all(action='DESELECT') | ||
|  |         bpy.ops.object.mode_set(mode='OBJECT') | ||
|  | 
 | ||
|  |         for edges in edgeDict: | ||
|  |             edgeList = edgeDict[edges] | ||
|  |             currentObject = bpy.context.scene.objects[edgeList['object']] | ||
|  |             bm = bmesh.new() | ||
|  |             bm.from_mesh(currentObject.data) | ||
|  |             if hasattr(bm.edges, "ensure_lookup_table"): | ||
|  |                 bm.edges.ensure_lookup_table() | ||
|  | 
 | ||
|  |             #assume that all the triangulated edges come after the original edges | ||
|  |             newEdges = [] | ||
|  |             for edge in range(len(edgeList['edges']), len(bm.edges)): | ||
|  |                 newEdge = bm.edges[edge] | ||
|  |                 newEdge.select = True | ||
|  |                 newEdges.append(newEdge) | ||
|  | 
 | ||
|  |             bmesh.ops.dissolve_edges(bm, edges=newEdges, use_verts=False, use_face_split=False) | ||
|  |             bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |             bm.to_mesh(currentObject.data) | ||
|  |             bm.free() | ||
|  |             bpy.ops.object.mode_set(mode='EDIT') | ||
|  | 
 | ||
|  |     #End setting the quads back again------------------------------------------------------------- | ||
|  | 
 | ||
|  |     #select the original objects that were selected | ||
|  |     for objectName in rename_dict: | ||
|  |         if objectName[0] in bpy.context.scene.objects: | ||
|  |             current_object = bpy.context.scene.objects[objectName[0]] | ||
|  |             current_object.select_set(True) | ||
|  |             context.view_layer.objects.active = current_object | ||
|  | 
 | ||
|  |     bpy.ops.object.mode_set(mode=startingMode) | ||
|  | 
 | ||
|  |     print("Finished Xatlas----------------------------------------") | ||
|  |     return {'FINISHED'} | ||
|  | 
 | ||
|  | def transfer_assets(copy, source, destination): | ||
|  |     for filename in glob.glob(os.path.join(source, '*.*')): | ||
|  |         try: | ||
|  |             shutil.copy(filename, destination) | ||
|  |         except shutil.SameFileError: | ||
|  |             pass | ||
|  | 
 | ||
|  | def transfer_load(): | ||
|  |     load_folder = bpy.path.abspath(os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_SceneProperties.tlm_load_folder)) | ||
|  |     lightmap_folder = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir) | ||
|  |     print(load_folder) | ||
|  |     print(lightmap_folder) | ||
|  |     transfer_assets(True, load_folder, lightmap_folder) |