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)
|