2025-12-01

This commit is contained in:
2026-03-17 14:58:51 -06:00
parent 183e865f8b
commit 4b82b57113
6846 changed files with 954887 additions and 162606 deletions
@@ -0,0 +1,65 @@
import bpy
from . import bake_utilities
from .. Functions import constants
from .. Functions import visibility_functions
def bake_texture(self, selected_objects, bake_settings):
parent_operator = self
# ----------------------- CREATE INSTANCE --------------------#
lightmap_utilities = bake_utilities.BakeUtilities(parent_operator, selected_objects, bake_settings)
if not lightmap_utilities.checkPBR():
return
# -----------------------SET LIGHTMAP UV--------------------#
lightmap_utilities.set_active_uv_to_lightmap()
# -----------------------SETUP UV'S--------------------#
lightmap_utilities.unwrap_selected()
# -----------------------SETUP ENGINE--------------------#
lightmap_utilities.setup_engine()
# -----------------------SWITCH BACK TO SHOW ORG MATERIAL --------------------#
visibility_functions.preview_bake_texture(self,context=bpy.context)
# ----------------------- CREATE NEW MATERIAL FOR BAKING --------------------#
lightmap_utilities.create_bake_material("_AO")
# -----------------------SETUP NODES--------------------#
lightmap_utilities.add_node_setup()
# ----------------------- BAKING --------------------#
if bake_settings.lightmap_bake:
lightmap_utilities.save_metal_value()
lightmap_utilities.bake(constants.Bake_Passes.lightmap)
lightmap_utilities.load_metal_value()
lightmap_utilities.add_lightmap_flag()
if bake_settings.ao_bake:
lightmap_utilities.bake(constants.Bake_Passes.ao)
lightmap_utilities.cleanup()
del lightmap_utilities
return
def bake_on_plane(self,selected_objects,bake_settings):
parent_operator = self
# ----------------------- CREATE INSTANCE --------------------#
pbr_utilities = bake_utilities.PbrBakeUtilities(parent_operator,selected_objects,bake_settings)
# -----------------------SETUP ENGINE--------------------#
pbr_utilities.setup_engine()
# ----------------------- BAKE --------------------#
pbr_utilities.bake_materials_on_object()
del pbr_utilities
return
@@ -0,0 +1,588 @@
import bpy
from bpy.types import ObjectShaderFx
import mathutils
from .. Functions import node_functions
from .. Functions import image_functions
from .. Functions import constants
from .. Functions import visibility_functions
from .. Functions import material_functions
blender_version = bpy.app.version
class BakeUtilities():
C = bpy.context
D = bpy.data
O = bpy.ops
all_materials = None
image_texture_nodes = None
bake_settings = None
bake_image = None
render_engine = None
selected_objects = None
image_size = None
parent_operator = None
tex_node_name = None
def __init__(self,parent_operator,selected_objects, bake_settings):
self.C = bpy.context
self.D = bpy.data
self.parent_operator = parent_operator
self.render_engine = self.C.scene.render.engine
self.selected_objects = selected_objects
self.all_materials = self.D.materials
self.selected_materials = material_functions.get_selected_materials(self.selected_objects)
self.bake_settings = bake_settings
self.baked_images = []
self.image_texture_nodes = set()
self.image_size = [int(self.C.scene.img_bake_size),
int(self.C.scene.img_bake_size)]
image_name = bake_settings.bake_image_name
self.bake_image = image_functions.create_image(image_name, self.image_size)
def setup_engine(self):
# setup engine
if self.render_engine == 'BLENDER_EEVEE':
self.C.scene.render.engine = 'CYCLES'
# setup device type
self.cycles_device_type = self.C.preferences.addons['cycles'].preferences.compute_device_type
if self.cycles_device_type == 'OPTIX':
self.C.preferences.addons['cycles'].preferences.compute_device_type = 'CUDA'
# setup samples
if self.bake_settings.pbr_bake:
self.C.scene.cycles.samples = self.bake_settings.pbr_samples
if self.bake_settings.lightmap_bake:
self.C.scene.cycles.samples = self.bake_settings.lightmap_samples
if self.bake_settings.ao_bake:
self.C.scene.cycles.samples = self.bake_settings.ao_samples
self.C.scene.render.resolution_percentage = 100
def set_active_uv_to_lightmap(self):
bpy.ops.object.set_active_uv(uv_slot=2)
def checkPBR(self):
for material in self.selected_materials:
self.active_material = material
# check if pbr node exists
check_ok = node_functions.check_pbr(self.parent_operator,material)
if not check_ok :
self.parent_operator.report({'INFO'}, "Material " + material.name + " has no PBR Node !")
return check_ok
def unwrap_selected(self):
if self.bake_settings.unwrap:
self.O.object.add_uv(uv_name=self.bake_settings.uv_name)
# apply scale on linked
sel_objects = self.C.selected_objects
scene_objects = self.D.objects
linked_objects = set()
for sel_obj in sel_objects:
for scene_obj in scene_objects:
if sel_obj.data.original is scene_obj.data and sel_obj is not scene_obj:
linked_objects.add(sel_obj)
# do not apply transform if linked objects in selection
if not len(linked_objects)>0:
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
self.O.object.mode_set(mode='EDIT')
self.O.mesh.reveal()
self.O.mesh.select_all(action='SELECT')
self.O.uv.smart_project(island_margin=self.bake_settings.unwrap_margin)
self.O.object.mode_set(mode='OBJECT')
def create_bake_material(self,material_name_suffix):
bake_materials = []
selected_materials = []
for obj in self.selected_objects:
for slot in obj.material_slots:
selected_materials.append(slot.material)
# switch to ao material if we are on org and ao was already baked
visibility_functions.switch_baked_material(True,"scene")
for obj in self.selected_objects:
for slot in obj.material_slots:
material = slot.material
bake_material_name = material.name + material_name_suffix
# check if material was already baked and continue
if material_name_suffix in material.name:
bake_materials.append(material)
continue
# if not, copy material or take one out of the previewsly filled bake list
else :
bake_material = list(filter(lambda material: material.name == bake_material_name, bake_materials))
if len(bake_material) == 0:
bake_material = material.copy()
bake_material.name = bake_material_name
bake_materials.append(bake_material)
slot.material = bake_material
else:
bake_material = bake_material[0]
slot.material = bake_material
index = bake_material.name.find(".")
if index == -1:
obj.bake_version = ""
else:
obj.bake_version = bake_material.name[index:]
material.use_fake_user = True
# remove duplicate entries
self.selected_materials = list(set(bake_materials))
def add_gltf_material_output_node(self, material):
nodes = material.node_tree.nodes
name = "glTF Material Output"
gltf_node_group = bpy.data.node_groups.new(name, 'ShaderNodeTree')
gltf_node_group.inputs.new("NodeSocketFloat", "Occlusion")
thicknessFactor = gltf_node_group.inputs.new("NodeSocketFloat", "Thickness")
thicknessFactor.default_value = 0.0
gltf_node_group.nodes.new('NodeGroupOutput')
gltf_node_group_input = gltf_node_group.nodes.new('NodeGroupInput')
specular = gltf_node_group.inputs.new("NodeSocketFloat", "Specular")
specular.default_value = 1.0
specularColor = gltf_node_group.inputs.new("NodeSocketColor", "Specular Color")
specularColor.default_value = [1.0,1.0,1.0,1.0]
gltf_node_group_input.location = -200, 0
gltf_settings_node = nodes.get(name)
if gltf_settings_node is None:
gltf_settings_node = nodes.new('ShaderNodeGroup')
gltf_settings_node.name = name
gltf_settings_node.node_tree = bpy.data.node_groups[name]
return gltf_settings_node
def add_gltf_settings_node(self, material):
nodes = material.node_tree.nodes
# 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
gltf_settings_node = nodes.get('glTF Settings')
if gltf_settings_node is None:
gltf_settings_node = nodes.new('ShaderNodeGroup')
gltf_settings_node.name = 'glTF Settings'
gltf_settings_node.node_tree = bpy.data.node_groups['glTF Settings']
# create group inputs
if gltf_settings_node.inputs.get('Occlusion') is None:
gltf_settings_node.inputs.new('NodeSocketFloat','Occlusion')
return gltf_settings_node
def add_image_texture_node(self, material):
nodes = material.node_tree.nodes
# add image texture
if self.bake_settings.lightmap_bake:
self.tex_node_name = self.bake_settings.texture_node_lightmap
if self.bake_settings.ao_bake:
self.tex_node_name = self.bake_settings.texture_node_ao
image_texture_node = node_functions.add_node(material, constants.Shader_Node_Types.image_texture, self.tex_node_name)
image_texture_node.image = self.bake_image
self.bake_image.colorspace_settings.name = "Linear FilmLight E-Gamut"
nodes.active = image_texture_node
# save texture nodes and pbr nodes for later
self.image_texture_nodes.add(image_texture_node)
return image_texture_node
def save_metal_value(self):
for material in self.selected_materials:
pbr_node = node_functions.get_pbr_node(material)
# save metal value
metallic_value = pbr_node.inputs["Metallic"].default_value
pbr_node["original_metallic"] = metallic_value
pbr_node.inputs["Metallic"].default_value = 0
# save metal image
if pbr_node.inputs["Metallic"].is_linked:
# get metal image node, save it in pbr node and remove connection
metal_image_node_socket = pbr_node.inputs["Metallic"].links[0].from_socket
self.metal_image_node_output = metal_image_node_socket
node_functions.remove_link(material,metal_image_node_socket,pbr_node.inputs["Metallic"])
def load_metal_value(self):
for material in self.selected_materials:
pbr_node = node_functions.get_pbr_node(material)
pbr_node.inputs["Metallic"].default_value = pbr_node["original_metallic"]
# reconnect metal image
if hasattr(self,"metal_image_node_output"):
node_functions.make_link(material,self.metal_image_node_output,pbr_node.inputs["Metallic"])
def add_uv_node(self,material):
uv_node = node_functions.add_node(material, constants.Shader_Node_Types.uv, "Second_UV")
uv_node.uv_map = self.bake_settings.uv_name
return uv_node
def position_gltf_setup_nodes(self,material,uv_node,image_texture_node,gltf_settings_node):
nodes = material.node_tree.nodes
# uv node
pbr_node = node_functions.get_pbr_node(material)
pos_offset = mathutils.Vector((-900, 400))
loc = pbr_node.location + pos_offset
uv_node.location = loc
# image texture
loc = loc + mathutils.Vector((300, 0))
image_texture_node.location = loc
# ao node
loc = loc + mathutils.Vector((300, 0))
gltf_settings_node.location = loc
nodes.active = image_texture_node
def add_node_setup(self):
for material in self.selected_materials:
# AO
if self.bake_settings.ao_bake:
uv_node = self.add_uv_node(material)
image_texture_node = self.add_image_texture_node(material)
if blender_version <= (3, 3):
gltf_settings_node = self.add_gltf_settings_node(material)
else:
gltf_settings_node = self.add_gltf_material_output_node(material)
# position
self.position_gltf_setup_nodes(material,uv_node,image_texture_node,gltf_settings_node)
# linking
node_functions.make_link(material, uv_node.outputs["UV"],image_texture_node.inputs['Vector'])
node_functions.make_link(material, image_texture_node.outputs['Color'], gltf_settings_node.inputs['Occlusion'])
# LIGHTMAP
if self.bake_settings.lightmap_bake:
image_texture_node = self.add_image_texture_node(material)
uv_node = self.add_uv_node(material)
# position
image_texture_node.location = mathutils.Vector((-500, 200))
uv_node.location = mathutils.Vector((-700, 200))
# linking
node_functions.make_link(material, uv_node.outputs["UV"],image_texture_node.inputs['Vector'])
def bake(self,bake_type):
channels_to_bake = bake_type
self.baked_images = []
denoise = self.bake_settings.denoise
# no denoise
if not denoise:
channel = bake_type[0]
image = self.bake_images(self.bake_image,channel,denoise)
image_functions.save_image(image,False)
return
# bake channels for denoise
for channel in channels_to_bake:
image_name = self.bake_image.name + "_" + channel
image = image_functions.create_image(image_name,self.bake_image.size)
self.change_image_in_nodes(image)
baked_channel_image = self.bake_images(image,channel,denoise)
image_functions.save_image(image,True)
self.baked_images.append(baked_channel_image)
self.denoise()
def change_image_in_nodes(self,image):
for image_texture_node in self.image_texture_nodes:
if image_texture_node.name == self.tex_node_name:
image_texture_node.image = image
def bake_images(self, image, channel,denoise):
if channel == "NRM":
print("Baking Normal Pass")
self.C.scene.cycles.samples = 1
self.O.object.bake(type="NORMAL", use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
if channel == "COLOR":
print("Baking Color Pass")
self.C.scene.cycles.samples = 1
self.O.object.bake(type="DIFFUSE", pass_filter={'COLOR'}, use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
if channel == "AO":
if not denoise:
self.O.object.bake('INVOKE_DEFAULT',type="AO", use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
else:
self.O.object.bake(type="AO", use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
if channel == "NOISY":
print("Baking Diffuse Pass")
if not denoise:
self.O.object.bake('INVOKE_DEFAULT',type="DIFFUSE", pass_filter={'DIRECT', 'INDIRECT'}, use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
else:
self.O.object.bake(type="DIFFUSE", pass_filter={'DIRECT', 'INDIRECT'}, use_clear=self.bake_settings.bake_image_clear, margin=self.bake_settings.bake_margin)
return image
def denoise(self):
# denoise
if self.bake_settings.lightmap_bake:
denoised_image_path = node_functions.comp_ai_denoise(self.baked_images[0],self.baked_images[1],self.baked_images[2])
self.bake_image.filepath = denoised_image_path
self.bake_image.source = "FILE"
self.change_image_in_nodes(self.bake_image)
# blur
if self.bake_settings.ao_bake and self.bake_settings.denoise:
blur_image_path = node_functions.blur_bake_image(self.baked_images[0],self.baked_images[1])
self.bake_image.filepath = blur_image_path
self.bake_image.source = "FILE"
self.change_image_in_nodes(self.bake_image)
def add_lightmap_flag(self):
for obj in self.selected_objects:
obj.hasLightmap = True
def cleanup(self):
# set back engine
# self.C.scene.render.engine = self.render_engine
self.C.preferences.addons['cycles'].preferences.compute_device_type = self.cycles_device_type
# cleanup images
if self.bake_settings.cleanup_textures:
for img in self.D.images:
if self.bake_image.name in img.name and ("_COLOR" in img.name or "_NRM" in img.name or "_NOISY" in img.name) :
self.D.images.remove(img)
# show image
visibility_functions.show_image_in_image_editor(self.bake_image)
class PbrBakeUtilities(BakeUtilities):
active_material = None
parent_operator = None
def __init__(self,parent_operator,selected_objects, bake_settings):
super().__init__(parent_operator,selected_objects,bake_settings)
self.selected_materials = material_functions.get_selected_materials(selected_objects)
self.parent_operator = parent_operator
def ready_for_bake(self,material):
# check if not baked material
if "_Bake" in material.name:
print("Skipping cause already baked : " + material.name)
return False
print("\n Checking " + material.name + "\n")
# check if renderer not set to optix
self.setup_engine()
# check if selected to active is on
bpy.context.scene.render.bake.use_selected_to_active = False
# check if pbr node exists
check_ok = node_functions.check_pbr(self.parent_operator,material) and node_functions.check_is_org_material(self.parent_operator,material)
if not check_ok :
self.parent_operator.report({'INFO'}, "Material " + material.name + " has no PBR Node !")
return False
# copy texture nodes if they are linked multiple times
nodes = material.node_tree.nodes
image_textrure_nodes = node_functions.get_nodes_by_type(nodes,constants.Node_Types.image_texture)
for image_texture_node in image_textrure_nodes:
node_functions.remove_double_linking(material,image_texture_node)
return True
def bake_materials_on_object(self):
for material in self.selected_materials:
self.active_material = material
if not (self.ready_for_bake(material)):
continue
self.add_bake_plane()
self.bake_pbr()
self.create_pbr_bake_material("_Bake")
self.create_nodes_after_pbr_bake()
self.cleanup_nodes()
visibility_functions.switch_baked_material(True,"visible")
def add_bake_plane(self):
material = self.active_material
bake_plane = self.D.objects.get(material.name + "_Bake")
if bake_plane is not None:
self.parent_operator.report({'INFO'}, 'Delete Bake Plane')
return
self.O.mesh.primitive_plane_add(size=2, location=(2, 0, 0))
bake_plane = self.C.object
bake_plane.name = material.name + "_Bake"
bake_plane.data.materials.append(material)
def bake_pbr(self):
material = self.active_material
material.use_fake_user = True
nodes = material.node_tree.nodes
pbr_node = node_functions.get_pbr_node(material)
pbr_inputs = node_functions.get_pbr_inputs(pbr_node)
image_texture_node = None
# mute texture mapping
if self.bake_settings.mute_texture_nodes:
node_functions.mute_all_texture_mappings(material, True)
for pbr_input in pbr_inputs.values():
# -----------------------TESTING--------------------#
# skip if input has no connection
if not pbr_input.is_linked:
continue
# -----------------------IMAGE --------------------#
image_name = material.name + "_" + pbr_input.name
# find image
bake_image = self.D.images.get(image_name)
# remove image
if bake_image is not None:
self.D.images.remove(bake_image)
bake_image = self.D.images.new(image_name, width=self.image_size[0], height=self.image_size[1])
bake_image.name = image_name
image_texture_node = node_functions.add_node(material,constants.Shader_Node_Types.image_texture,"PBR Bake")
image_texture_node.image = bake_image
nodes.active = image_texture_node
# -----------------------SET COLOR SPACE--------------------#
if pbr_input is not pbr_inputs["base_color_input"]:
bake_image.colorspace_settings.name = "Non-Color"
# -----------------------BAKING--------------------#
if pbr_input is pbr_inputs["normal_input"]:
node_functions.link_pbr_to_output(material, pbr_node)
self.O.object.bake(type="NORMAL", use_clear=True)
else:
node_functions.emission_setup(material, pbr_input.links[0].from_socket)
self.O.object.bake(type="EMIT", use_clear=True)
# unmute texture mappings
node_functions.mute_all_texture_mappings(material, False)
# delete plane
self.O.object.delete()
# cleanup nodes
node_functions.remove_node(material,"Emission Bake")
node_functions.remove_node(material,"PBR Bake")
node_functions.reconnect_PBR(material, pbr_node)
def create_pbr_bake_material(self,material_name_suffix):
# -----------------------CREATE MATERIAL--------------------#
org_material = self.active_material
bake_material_name = org_material.name + material_name_suffix
bake_material = bpy.data.materials.get(bake_material_name)
if bake_material is not None:
bpy.data.materials.remove(bake_material)
# and create new from org. material
bake_material = org_material.copy()
bake_material.name = bake_material_name
self.bake_material = bake_material
def create_nodes_after_pbr_bake(self):
# -----------------------SETUP VARS--------------------#
org_material = self.active_material
bake_material = self.bake_material
nodes = bake_material.node_tree.nodes
pbr_node = node_functions.get_pbr_node(bake_material)
pbr_inputs = node_functions.get_pbr_inputs(pbr_node)
for pbr_input in pbr_inputs.values():
if not pbr_input.is_linked:
continue
# -----------------------REPLACE IMAGE TEXTURES--------------------#
first_node_after_input = pbr_input.links[0].from_node
tex_node = node_functions.get_node_by_type_recusivly(bake_material,first_node_after_input,constants.Node_Types.image_texture,True)
bake_image_name = org_material.name + "_" + pbr_input.name
bake_image = self.D.images.get(bake_image_name)
# if no texture node found (baking procedural textures) add new one
if tex_node is None:
tex_node = node_functions.add_node(bake_material,constants.Shader_Node_Types.image_texture,bake_image_name)
# keep org image if nothing changed
if bake_image is None:
org_image = self.D.images.get(tex_node.image.org_image_name)
tex_node.image = org_image
else:
image_functions.save_image(bake_image)
tex_node.image = bake_image
# -----------------------LINKING--------------------#
if pbr_input is pbr_inputs["normal_input"]:
normal_node = first_node_after_input
normal_node.inputs["Strength"].default_value = 1
if normal_node.type == constants.Node_Types.bump_map:
bump_node = normal_node
normal_node = node_functions.add_node(bake_material,constants.Shader_Node_Types.normal,"Normal from Bump")
normal_node.location = bump_node.location
nodes.remove(bump_node)
node_functions.make_link(bake_material, tex_node.outputs[0], normal_node.inputs["Color"])
node_functions.make_link(bake_material, normal_node.outputs["Normal"], pbr_input)
else:
node_functions.make_link(bake_material,tex_node.outputs[0], pbr_input)
# -----------------------SET COLOR SPACE--------------------#
if pbr_input is not pbr_inputs["base_color_input"]:
tex_node.image.colorspace_settings.name = "Non-Color"
self.active_material = bake_material
return bake_material
def cleanup_nodes(self):
bake_material = self.active_material
node_functions.remove_unused_nodes(bake_material)
+1
View File
@@ -0,0 +1 @@
lorenz.wieseke.glbtexturetools
@@ -0,0 +1,13 @@
def Diff(li1, li2):
return (list(set(li1) - set(li2)))
def Intersection(li1, li2):
set1 = set(li1)
set2 = set(li2)
return list(set.intersection(set1,set2))
def flatten(t):
return [item for sublist in t for item in sublist]
def remove_duplicate(l):
return list(dict.fromkeys(l))
@@ -0,0 +1,39 @@
import os
import bpy
class Node_Types:
image_texture = 'TEX_IMAGE'
pbr_node = 'BSDF_PRINCIPLED'
mapping = 'MAPPING'
normal_map = 'NORMAL_MAP'
bump_map = 'BUMP'
material_output = 'OUTPUT_MATERIAL'
class Shader_Node_Types:
emission = "ShaderNodeEmission"
image_texture = "ShaderNodeTexImage"
mapping = "ShaderNodeMapping"
normal = "ShaderNodeNormalMap"
ao = "ShaderNodeAmbientOcclusion"
uv = "ShaderNodeUVMap"
comp_image_node = 'CompositorNodeImage'
mix ="ShaderNodeMixRGB"
class Bake_Passes:
pbr = ["EMISSION"]
lightmap = ["NOISY", "NRM", "COLOR"]
ao = ["AO","COLOR"]
class Material_Suffix:
bake_type_mat_suffix = {
"pbr" : "_Bake",
"ao" : "_AO",
"lightmap" : "_AO"
}
class Path_List:
def get_project_dir():
return os.path.dirname(bpy.data.filepath)
def get_textures_dir():
return os.path.join(os.path.dirname(bpy.data.filepath),'textures','GLBTexTool')
@@ -0,0 +1,91 @@
import bpy
from bpy.app.handlers import persistent
def update_pbr_button(self,context):
self["lightmap_bake"] = False
self["ao_bake"] = False
def update_lightmap_button(self,context):
self["pbr_bake"] = False
self["ao_bake"] = False
update_active_element_in_bake_list()
def update_ao_button(self,context):
self["lightmap_bake"] = False
self["pbr_bake"] = False
update_active_element_in_bake_list()
# ----------------------- UPDATE BAKE IMAGE NAME / ENUM--------------------#
last_selection = []
@persistent
def update_on_selection(scene):
C = bpy.context
global last_selection
object = getattr(C,"object",None)
if object is None:
return
if C.selected_objects != last_selection:
last_selection = C.selected_objects
update_active_element_in_bake_list()
def update_bake_list(bake_settings,context):
bake_textures_set = set()
for obj in bpy.data.objects:
if bake_settings.lightmap_bake:
if obj.get("lightmap_name"):
bake_textures_set.add((obj.lightmap_name, obj.lightmap_name, "Baked Texture Name"))
if bake_settings.ao_bake:
if obj.get("ao_map_name"):
bake_textures_set.add((obj.ao_map_name, obj.ao_map_name, "Baked Texture Name"))
if len(bake_textures_set) == 0:
bake_textures_set.add(("-- Baking Groups --","-- Baking Groups --","No Lightmap baked yet"))
return list(bake_textures_set)
def update_active_element_in_bake_list():
C = bpy.context
active_object = C.active_object
bake_settings = C.scene.bake_settings
new_bake_image_name = ""
if bake_settings.lightmap_bake:
new_bake_image_name = active_object.get("lightmap_name")
if new_bake_image_name is None:
new_bake_image_name = "Lightmap " + active_object.name
if bake_settings.ao_bake:
new_bake_image_name = active_object.get("ao_map_name")
if new_bake_image_name is None:
new_bake_image_name = "AO " + active_object.name
enum_items = bake_settings.get_baked_lightmaps()
keys = [key[0] for key in enum_items]
if new_bake_image_name in keys:
bake_settings.bake_image_name = new_bake_image_name
bake_settings.baking_groups = new_bake_image_name
else:
if active_object.type == "MESH":
bake_settings.bake_image_name = new_bake_image_name
def headline(layout,*valueList):
box = layout.box()
row = box.row()
split = row.split()
for pair in valueList:
split = split.split(factor=pair[0])
split.label(text=pair[1])
@persistent
def init_values(self,context):
bpy.context.scene.world.light_settings.distance = 1
@@ -0,0 +1,107 @@
import bpy
import os
from . import node_functions
from . import constants
from bpy.app.handlers import persistent
@persistent
def save_images(self,context):
images = bpy.data.images
for img in images:
if img.is_dirty:
print(img.name)
save_image(img) # img.save()
def get_all_images_in_ui_list():
images_in_scene = bpy.data.images
image_name_list = bpy.types.GTT_TEX_UL_List.image_name_list
images_found = []
if len(image_name_list) > 0:
images_found = [img for img in images_in_scene for name_list_entry in image_name_list if img.name == name_list_entry]
return images_found
def save_image(image,save_internally=False):
if save_internally:
image.pack()
else:
filePath = bpy.data.filepath
path = os.path.dirname(filePath)
if not os.path.exists(path + "/textures"):
os.mkdir(path + "/textures")
if not os.path.exists(path + "/textures/GLBTexTool"):
os.mkdir(path + "/textures/GLBTexTool")
if not os.path.exists(path + "/textures/GLBTexTool/" + str(image.size[0])):
os.mkdir(path + "/textures/GLBTexTool/" + str(image.size[0]))
# file format
image.file_format = bpy.context.scene.img_file_format
# change path
savepath = path + "\\textures\\GLBTexTool\\" + str(image.size[0]) + "\\" + image.name + "." + image.file_format
# image.use_fake_user = True
image.filepath_raw = savepath
image.save()
def create_image(image_name, image_size):
D = bpy.data
# find image
image = D.images.get(image_name)
if image:
old_size = list(image.size)
new_size = list(image_size)
if old_size != new_size:
D.images.remove(image)
image = None
# image = D.images.get(image_name)
if image is None:
image = D.images.new(
image_name, width=image_size[0], height=image_size[1])
image.name = image_name
return image
def get_file_size(filepath):
size = "Unpack Files"
try:
path = bpy.path.abspath(filepath)
size = os.path.getsize(path)
size /= 1024
except:
return ("Unpack")
# print("error getting file path for " + filepath)
return (size)
def scale_image(image, new_size):
if (image.org_filepath != ''):
image.filepath = image.org_filepath
image.org_filepath = image.filepath
if new_size[0] > image.size[0] or new_size[1] > image.size[1]:
new_size[0] = image.size[0]
new_size[1] = image.size[1]
# set image back to original if size is 0, else scale it
if new_size[0] == 0:
image.filepath_raw = image.org_filepath
else:
image.scale(new_size[0], new_size[1])
save_image(image)
@@ -0,0 +1,40 @@
import bpy
def get_all_visible_materials():
objects=[ob for ob in bpy.context.view_layer.objects if ob.visible_get()]
slot_array = [object.material_slots for object in objects]
vis_mat = set()
for slots in slot_array:
for slot in slots:
vis_mat.add(slot.material)
# to remove None values in list
vis_mat = list(filter(None, vis_mat))
return vis_mat
def get_selected_materials(selected_objects):
selected_materials = set()
slots_array = [obj.material_slots for obj in selected_objects]
for slots in slots_array:
for slot in slots:
selected_materials.add(slot.material)
return selected_materials
def clean_empty_materials():
for obj in bpy.data.objects:
for slot in obj.material_slots:
mat = slot.material
if mat is None:
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 clean_no_user_materials():
for material in bpy.data.materials:
if not material.users:
bpy.data.materials.remove(material)
def use_nodes():
for material in bpy.data.materials:
if not material.use_nodes:
material.use_nodes = True
@@ -0,0 +1,390 @@
import bpy.ops as O
import bpy
import os
from .. Functions import constants
import mathutils
# -----------------------COMPOSITING--------------------#
def blur_bake_image(noisy_image,color_image):
# switch on nodes and get reference
if not bpy.context.scene.use_nodes:
bpy.context.scene.use_nodes = True
tree = bpy.context.scene.node_tree
# add cam if not in scene
cam = bpy.context.scene.camera
if not cam:
bpy.ops.object.camera_add()
# bake image
image_node = tree.nodes.new(type='CompositorNodeImage')
image_node.image = noisy_image
image_node.location = 0, 0
# color image
color_image_node = tree.nodes.new(type='CompositorNodeImage')
color_image_node.image = color_image
color_image_node.location = 0, 300
# create blur node
blur_node = tree.nodes.new(type='CompositorNodeBilateralblur')
blur_node.location = 300, 0
# create output node
comp_node = tree.nodes.new('CompositorNodeComposite')
comp_node.location = 600, 0
# link nodes
links = tree.links
links.new(image_node.outputs[0], blur_node.inputs[0])
links.new(color_image_node.outputs[0], blur_node.inputs[1])
links.new(blur_node.outputs[0], comp_node.inputs[0])
# set output resolution to image res
bpy.context.scene.render.resolution_x = noisy_image.size[0]
bpy.context.scene.render.resolution_y = noisy_image.size[1]
# set output path
scene = bpy.context.scene
outputImagePath = constants.Path_List.get_textures_dir()
# set image format and quality
scene.render.image_settings.file_format = bpy.context.scene.img_file_format
scene.render.image_settings.quality = 100
scene.render.filepath = os.path.join(outputImagePath,noisy_image.name + "_Denoise_AO")
bpy.ops.render.render(write_still=True)
if bpy.context.scene.img_file_format == 'JPEG':
file_extention = '.jpg'
elif bpy.context.scene.img_file_format == 'PNG':
file_extention = '.png'
elif bpy.context.scene.img_file_format == 'HDR':
file_extention = '.hdr'
# cleanup
comp_nodes = [image_node,color_image_node,blur_node,comp_node]
for node in comp_nodes:
tree.nodes.remove(node)
return scene.render.filepath + file_extention
def comp_ai_denoise(noisy_image, nrm_image, color_image):
# switch on nodes and get reference
if not bpy.context.scene.use_nodes:
bpy.context.scene.use_nodes = True
tree = bpy.context.scene.node_tree
# add cam if not in scene
cam = bpy.context.scene.camera
if not cam:
bpy.ops.object.camera_add()
# bake image
image_node = tree.nodes.new(type='CompositorNodeImage')
image_node.image = noisy_image
image_node.location = 0, 0
# nrm image
nrm_image_node = tree.nodes.new(type='CompositorNodeImage')
nrm_image_node.image = nrm_image
nrm_image_node.location = 0, 300
# color image
color_image_node = tree.nodes.new(type='CompositorNodeImage')
color_image_node.image = color_image
color_image_node.location = 0, 600
# create denoise node
denoise_node = tree.nodes.new(type='CompositorNodeDenoise')
denoise_node.location = 300, 0
# create output node
comp_node = tree.nodes.new('CompositorNodeComposite')
comp_node.location = 600, 0
# link nodes
links = tree.links
links.new(image_node.outputs[0], denoise_node.inputs[0])
links.new(nrm_image_node.outputs[0], denoise_node.inputs[1])
links.new(color_image_node.outputs[0], denoise_node.inputs[2])
links.new(denoise_node.outputs[0], comp_node.inputs[0])
# set output resolution to image res
bpy.context.scene.render.resolution_x = noisy_image.size[0]
bpy.context.scene.render.resolution_y = noisy_image.size[1]
# set output path
scene = bpy.context.scene
outputImagePath = constants.Path_List.get_textures_dir()
# set image format and quality
scene.render.image_settings.file_format = bpy.context.scene.img_file_format
scene.render.image_settings.quality = 100
scene.render.filepath = os.path.join(outputImagePath,noisy_image.name + "_Denoise_LM")
print("Starting Denoise")
bpy.ops.render.render(write_still=True)
if bpy.context.scene.img_file_format == 'JPEG':
file_extention = '.jpg'
elif bpy.context.scene.img_file_format == 'PNG':
file_extention = '.png'
elif bpy.context.scene.img_file_format == 'HDR':
file_extention = '.hdr'
# cleanup
comp_nodes = [image_node, nrm_image_node,color_image_node, denoise_node, comp_node]
for node in comp_nodes:
tree.nodes.remove(node)
return scene.render.filepath + file_extention
# -----------------------CHECKING --------------------#
def check_pbr(self, material):
check_ok = True
if material is None:
return False
if material.node_tree is None:
return False
if material.node_tree.nodes is None:
return False
# get pbr shader
nodes = material.node_tree.nodes
pbr_node_type = constants.Node_Types.pbr_node
pbr_nodes = get_nodes_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
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
# -----------------------NODES --------------------#
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 Tint"]
roughness_input = pbr_node.inputs["Roughness"]
normal_input = pbr_node.inputs["Normal"]
emission_input = pbr_node.inputs["Emission Color"]
alpha_input = pbr_node.inputs["Alpha"]
pbr_inputs = {"base_color_input": base_color_input, "metallic_input": metallic_input,
"specular_input": specular_input, "roughness_input": roughness_input,
"normal_input": normal_input, "emission_input": emission_input,"alpha_input":alpha_input}
return pbr_inputs
def get_nodes_by_type(nodes, node_type):
nodes_found = [n for n in nodes if n.type == node_type]
return nodes_found
def get_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 != constants.Node_Types.normal_map and note_to_start.type != constants.Node_Types.bump_map):
nodes.remove(note_to_start)
return get_node_by_type_recusivly(material, current_node, node_type, del_nodes_inbetween)
def get_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 get_node_by_name_recusivly(current_node, idname)
def get_pbr_node(material):
nodes = material.node_tree.nodes
pbr_node = get_nodes_by_type(nodes, constants.Node_Types.pbr_node)
if len(pbr_node) > 0:
return pbr_node[0]
def make_link(material, socket1, socket2):
links = material.node_tree.links
links.new(socket1, socket2)
def remove_link(material, socket1, socket2):
node_tree = material.node_tree
links = node_tree.links
for l in socket1.links:
if l.to_socket == socket2:
links.remove(l)
def add_in_gamme_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 emission_setup(material, node_output):
nodes = material.node_tree.nodes
emission_node = add_node(
material, constants.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 = get_nodes_by_type(nodes,constants.Node_Types.material_output)[0].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 = get_nodes_by_type(nodes,constants.Node_Types.material_output)[0].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 = get_nodes_by_type(
nodes, constants.Node_Types.material_output)[0].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 remove_reconnect_node(material, node_name):
nodes = material.node_tree.nodes
node = nodes.get(node_name)
input_node = node.inputs["Color1"].links[0].from_node
output_node = node.outputs["Color"].links[0].to_node
if node is not None:
make_link(material,input_node.outputs["Color"],output_node.inputs["Base Color"])
nodes.remove(node)
def remove_unused_nodes(material):
nodes = material.node_tree.nodes
all_nodes = set(nodes)
connected_nodes = set()
material_output = nodes.get("Material Output")
get_all_connected_nodes(material_output, connected_nodes)
unconnected_nodes = all_nodes - connected_nodes
for node in unconnected_nodes:
nodes.remove(node)
def remove_double_linking(material,texture_node):
color_output = texture_node.outputs["Color"]
links_count = len(color_output.links)
org_vector_input = texture_node.inputs["Vector"]
position_y = texture_node.location.y
if links_count > 1:
for link in color_output.links:
new_texture_node = add_node(material,constants.Shader_Node_Types.image_texture,texture_node.name + "_Copy" + str(link))
new_texture_node.image = texture_node.image
new_texture_node.location = texture_node.location
position_y -= 250
new_texture_node.location.y = position_y
# relink tex node output
make_link(material,new_texture_node.outputs["Color"],link.to_socket)
# remap texture mapping
if len(org_vector_input.links) != 0:
new_vector_input = new_texture_node.inputs["Vector"]
tex_transform_socket = org_vector_input.links[0].from_socket
make_link(material,tex_transform_socket,new_vector_input)
def get_all_connected_nodes(node, connected_nodes):
connected_nodes.add(node)
for input in node.inputs:
for link in input.links:
current_node = link.from_node
get_all_connected_nodes(current_node, connected_nodes)
@@ -0,0 +1,36 @@
import bpy
from .. Functions import node_functions
def apply_transform_on_linked():
bpy.ops.object.select_linked(type='OBDATA')
bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', object=True, obdata=True, material=False, animation=False)
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
bpy.ops.object.make_links_data(type='OBDATA')
def select_object(self, obj):
C = bpy.context
O = bpy.ops
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(mat,self=None):
D = bpy.data
result = []
for obj in D.objects:
if obj.type == "MESH":
object_materials = [slot.material for slot in obj.material_slots]
if mat in object_materials:
result.append(obj)
if (self):
select_object(self, obj)
return result
# TODO - save objects in array, unlink objects, apply scale and link them back
def apply_scale_on_multiuser():
O = bpy.ops
O.object.transform_apply(location=False, rotation=False, scale=True)
@@ -0,0 +1,200 @@
import bpy
from bpy import context
from . import node_functions
from . import material_functions
from . import object_functions
from . import constants
from . import basic_functions
import mathutils
def update_selected_image(self, context):
sel_texture = bpy.data.images[self.texture_index]
show_image_in_image_editor(sel_texture)
def show_image_in_image_editor(image):
for area in bpy.context.screen.areas:
if area.type == 'IMAGE_EDITOR':
area.spaces.active.image = image
def switch_baked_material(show_bake_material,affect):
current_bake_type = bpy.context.scene.bake_settings.get_current_bake_type()
material_name_suffix = constants.Material_Suffix.bake_type_mat_suffix[current_bake_type]
# on what object to work
if affect == 'active':
objects = [bpy.context.active_object]
elif affect == 'selected':
objects = bpy.context.selected_editable_objects
elif affect == 'visible':
objects = [ob for ob in bpy.context.view_layer.objects if ob.visible_get()]
elif affect == 'scene':
objects = bpy.context.scene.objects
elif affect == 'linked':
objects = []
selected_objects = bpy.context.selected_editable_objects
selected_materials = material_functions.get_selected_materials(selected_objects)
for material in selected_materials:
objs = object_functions.select_obj_by_mat(material)
objects.append(objs)
objects = basic_functions.flatten(objects)
objects = basic_functions.remove_duplicate(objects)
all_mats = bpy.data.materials
baked_mats = [mat for mat in all_mats if material_name_suffix in mat.name]
for obj in objects:
# if current_bake_type != "pbr":
# baked_ao_flag = getattr(obj,"ao_map_name") != '' or getattr(obj,"lightmap_name") != ''
# if not baked_ao_flag:
# continue
for slot in obj.material_slots:
if show_bake_material:
for baked_mat in baked_mats:
try:
if baked_mat.name == slot.material.name + material_name_suffix + obj.bake_version:
slot.material = baked_mat
except:
pass
else:
if (material_name_suffix in slot.material.name):
bake_material = slot.material
index = bake_material.name.find(material_name_suffix)
org_mat = all_mats.get(bake_material.name[0:index])
if org_mat is not None:
slot.material = org_mat
def preview_bake_texture(self,context):
context = bpy.context
bake_settings = context.scene.bake_settings
preview_bake_texture = context.scene.texture_settings.preview_bake_texture
vis_mats = material_functions.get_all_visible_materials()
for mat in vis_mats:
if not mat.node_tree:
continue
nodes = mat.node_tree.nodes
bake_texture_node = None
if bake_settings.lightmap_bake:
bake_texture_node = nodes.get(bake_settings.texture_node_lightmap)
elif bake_settings.ao_bake:
bake_texture_node = nodes.get(bake_settings.texture_node_ao)
if bake_texture_node is not None:
if preview_bake_texture:
node_functions.emission_setup(mat, bake_texture_node.outputs["Color"])
else:
pbr_node = node_functions.get_nodes_by_type(nodes, constants.Node_Types.pbr_node)
if len(pbr_node) == 0:
return
pbr_node = pbr_node[0]
node_functions.remove_node(mat, "Emission Bake")
node_functions.reconnect_PBR(mat, pbr_node)
def preview_lightmap(self, context):
preview_lightmap = context.scene.texture_settings.preview_lightmap
vis_mats = material_functions.get_all_visible_materials()
for material in vis_mats:
if not material.node_tree:
continue
nodes = material.node_tree.nodes
lightmap_node = nodes.get("Lightmap")
if lightmap_node is None:
continue
pbr_node = node_functions.get_pbr_node(material)
if pbr_node is None:
print("\n " + material.name + " has no PBR Node \n")
continue
base_color_input = node_functions.get_pbr_inputs(pbr_node)["base_color_input"]
emission_input = node_functions.get_pbr_inputs(pbr_node)["emission_input"]
lightmap_output = lightmap_node.outputs["Color"]
if preview_lightmap:
# add mix node
mix_node_name = "Mulitply Lightmap"
mix_node = node_functions.add_node(material,constants.Shader_Node_Types.mix, mix_node_name)
mix_node.blend_type = 'MULTIPLY'
mix_node.inputs[0].default_value = 1 # set factor to 1
pos_offset = mathutils.Vector((-200, 200))
mix_node.location = pbr_node.location + pos_offset
mix_node_input1 = mix_node.inputs["Color1"]
mix_node_input2 = mix_node.inputs["Color2"]
mix_node_output = mix_node.outputs["Color"]
# image texture in base color
if base_color_input.is_linked:
node_before_base_color = base_color_input.links[0].from_node
if not node_before_base_color.name == mix_node_name:
node_functions.make_link(material, node_before_base_color.outputs["Color"], mix_node_input1)
node_functions.make_link(material, lightmap_output, mix_node_input2)
node_functions.make_link(material, mix_node_output, base_color_input)
else :
mix_node_input1.default_value = base_color_input.default_value
node_functions.make_link(material, lightmap_output, mix_node_input2)
node_functions.make_link(material, mix_node_output, base_color_input)
node_functions.remove_link(material,lightmap_output,emission_input)
if not preview_lightmap:
# remove mix and reconnect base color
mix_node = nodes.get("Mulitply Lightmap")
if mix_node is not None:
color_input_connections = len(mix_node.inputs["Color1"].links)
if (color_input_connections == 0):
node_functions.remove_node(material,mix_node.name)
else:
node_functions.remove_reconnect_node(material,mix_node.name)
node_functions.link_pbr_to_output(material,pbr_node)
def lightmap_to_emission(self, context, connect):
vis_mats = material_functions.get_all_visible_materials()
for material in vis_mats:
if not material.node_tree:
continue
nodes = material.node_tree.nodes
pbr_node = node_functions.get_pbr_node(material)
lightmap_node = nodes.get("Lightmap")
if lightmap_node is None:
continue
emission_input = node_functions.get_pbr_inputs(pbr_node)["emission_input"]
lightmap_output = lightmap_node.outputs["Color"]
if connect:
node_functions.make_link(material, lightmap_output, emission_input)
else:
node_functions.remove_link(material,lightmap_output,emission_input)
@@ -0,0 +1,20 @@
import bpy
addon_keymaps = []
# keymap
def register():
kcfg = bpy.context.window_manager.keyconfigs.addon
if kcfg:
km = kcfg.keymaps.new(name='3D View', space_type='VIEW_3D')
kmi = km.keymap_items.new("scene.node_to_texture_operator", 'B', 'PRESS', shift=True, ctrl=True)
addon_keymaps.append((km, kmi))
def unregister():
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Lorenz Wieseke
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,68 @@
import bpy
from ..Bake import bake_manager
from .. Functions import gui_functions
class GTT_BakeOperator(bpy.types.Operator):
"""Bake all attached Textures"""
bl_idname = "object.gtt_bake_operator"
bl_label = "Simple Object Operator"
@classmethod
def poll(cls, context):
if len(context.selected_objects) < 1:
return False
else:
return True
def deselect_everything_but_mesh(self,selected_objects):
for obj in selected_objects:
if obj.type != 'MESH':
obj.select_set(False)
def execute(self,context):
# ----------------------- VAR --------------------#
selected_objects = context.selected_objects
bake_settings = context.scene.bake_settings
texture_settings = context.scene.texture_settings
self.deselect_everything_but_mesh(selected_objects)
# ----------------------- CHECK SELECTION --------------------#
for obj in selected_objects:
if obj.type != 'MESH':
obj.select_set(False)
if len(obj.material_slots) == 0:
self.report({'INFO'}, 'No Material on ' + obj.name)
context.view_layer.objects.active = context.selected_objects[0]
# ----------------------- SET VISIBLITY TO MATERIAL --------------------#
texture_settings.preview_bake_texture = False
# ----------------------- LIGHTMAP / AO --------------------#
if bake_settings.lightmap_bake or bake_settings.ao_bake:
bake_manager.bake_texture(self,selected_objects,bake_settings)
if bake_settings.show_texture_after_bake and bake_settings.denoise:
texture_settings.preview_bake_texture = True
for obj in selected_objects:
if bake_settings.ao_bake:
obj.ao_map_name = bake_settings.bake_image_name
if bake_settings.lightmap_bake:
obj.lightmap_name = bake_settings.bake_image_name
gui_functions.update_active_element_in_bake_list()
# ----------------------- PBR Texture --------------------#
if bake_settings.pbr_bake:
bake_manager.bake_on_plane(self,selected_objects,bake_settings)
return {'FINISHED'}
@@ -0,0 +1,19 @@
from bpy.ops import OBJECT_OT_bake as op
def callback(ret):
print('Callback triggered: {} !!'.format(ret))
def modal_wrap(modal_func, callback):
def wrap(self, context, event):
ret, = retset = modal_func(self, context, event)
if ret in {'CANCELLED'}: # my plugin emits the CANCELED event on finish - yours might do FINISH or FINISHED, you might have to look it up in the source code, __init__.py , there look at the modal() function for things like return {'FINISHED'} or function calls that return things alike.
print(f"{self.bl_idname} returned {ret}")
callback(ret)
return retset
return wrap
# op._modal_org = op.modal
op.modal = modal_wrap(op.modal, callback)
@@ -0,0 +1,465 @@
import os
import subprocess
import bpy
from ..Functions import (basic_functions, constants,
image_functions, material_functions, node_functions,
visibility_functions,object_functions)
class GTT_VerifyMaterialsOperator(bpy.types.Operator):
"""Check for each visbile material if it has a PBR Shader so the GLTF Export works fine"""
bl_idname = "object.verify_materials"
bl_label = "Verify Materials"
def execute(self, context):
vis_mats = material_functions.get_all_visible_materials()
for mat in vis_mats:
check_ok = node_functions.check_pbr(self,mat)
if not check_ok:
self.report({'INFO'}, "No PBR Shader in " + mat.name)
objects_in_scene = bpy.data.objects
for obj in objects_in_scene:
for slot in obj.material_slots:
if slot.material is None:
self.report({'INFO'}, "Empty Material Slot on " + obj.name)
return {'FINISHED'}
# ----------------------- LIGHTAP OPERATORS--------------------#
class GTT_SelectLightmapObjectsOperator(bpy.types.Operator):
"""Select all Objects in the list that have the according lightmap attached to them. Makes it easy to rebake multiple Objects"""
bl_idname = "object.select_lightmap_objects"
bl_label = "Select Lightmap Objects"
@classmethod
def poll(cls, context):
if context.scene.bake_settings.baking_groups == '-- Baking Groups --':
return False
return True
def execute(self, context):
C = context
O = bpy.ops
bake_settings = C.scene.bake_settings
active_lightmap = bake_settings.baking_groups
objects = [ob for ob in bpy.context.view_layer.objects if ob.visible_get()]
O.object.select_all(action='DESELECT')
for obj in objects:
if obj.lightmap_name == active_lightmap or obj.ao_map_name == active_lightmap:
C.view_layer.objects.active = obj
obj.select_set(True)
return {'FINISHED'}
# ----------------------- TEXTURE OPERATORS--------------------#
class GTT_GetMaterialByTextureOperator(bpy.types.Operator):
bl_idname = "scene.select_mat_by_tex"
bl_label = "Select Material By Texture"
bl_description = "Selecting all materials in scene that use the selected texture"
bl_options = {"REGISTER"}
bpy.types.Scene.materials_found = []
@classmethod
def poll(cls, context):
D = bpy.data
images = D.images
display = True
# image to index not found
try:
sel_image_texture = images[context.scene.texture_settings.texture_index]
if sel_image_texture.name in ('Viewer Node', 'Render Result'):
display = False
except:
display = False
return display
def execute(self, context):
D = bpy.data
images = D.images
sel_image_texture = images[context.scene.texture_settings.texture_index]
materials = D.materials
# to print materials with current image texture
materials_found = context.scene.materials_found
materials_found.clear()
for mat in materials:
if hasattr(mat,"node_tree"):
if hasattr(mat.node_tree,"nodes"):
nodes = mat.node_tree.nodes
tex_node_type = constants.Node_Types.image_texture
tex_nodes = node_functions.get_nodes_by_type(nodes,tex_node_type)
# if texture node in current node tree
if len(tex_nodes) > 0:
images = [node.image for node in tex_nodes]
if sel_image_texture in images:
materials_found.append(mat.name)
object_functions.select_obj_by_mat(mat,self)
return {"FINISHED"}
class GTT_ScaleImageOperator(bpy.types.Operator):
"""Scale all Images on selected Material to specific resolution"""
bl_idname = "image.scale_image"
bl_label = "Scale Images"
@classmethod
def poll(cls, context):
D = bpy.data
images = D.images
display = True
# if operate on all textures is checked we don't need to get the index
if context.scene.texture_settings.operate_on_all_textures:
return True
# image to index not found
try:
sel_image_texture = images[context.scene.texture_settings.texture_index]
if sel_image_texture.name in ('Viewer Node','Render Result'):
display = False
except:
display = False
return display
def invoke(self,context,event):
D = bpy.data
texture_settings = context.scene.texture_settings
image_size = [int(context.scene.img_bake_size),int(context.scene.img_bake_size)]
images_in_scene = D.images
all_images = image_functions.get_all_images_in_ui_list()
if texture_settings.operate_on_all_textures:
for img in all_images:
image_functions.scale_image(img,image_size)
else:
sel_image_texture = images_in_scene[texture_settings.texture_index]
image_functions.scale_image(sel_image_texture,image_size)
return {'FINISHED'}
def modal(self, context, event):
if event.type in {'RIGHTMOUSE', 'ESC'}:
return {'CANCELLED'}
return {'PASS_THROUGH'}
# ----------------------- VIEW OPERATORS--------------------#
class GTT_SwitchBakeMaterialOperator(bpy.types.Operator):
"""Switch to baked material"""
bl_idname = "object.switch_bake_mat_operator"
bl_label = "Baked Material"
def execute(self, context):
show_bake_material = True
visibility_functions.switch_baked_material(show_bake_material,context.scene.affect)
return {'FINISHED'}
class GTT_SwitchOrgMaterialOperator(bpy.types.Operator):
"""Switch from baked to original material"""
bl_idname = "object.switch_org_mat_operator"
bl_label = "Org. Material"
def execute(self, context):
show_bake_material = False
visibility_functions.switch_baked_material(show_bake_material,context.scene.affect)
return {'FINISHED'}
class GTT_PreviewBakeTextureOperator(bpy.types.Operator):
"""Connect baked texture to emission to see result"""
bl_idname = "object.preview_bake_texture"
bl_label = "Preview Bake Texture"
connect : bpy.props.BoolProperty()
def execute(self, context):
context.scene.texture_settings.preview_bake_texture = self.connect
visibility_functions.preview_bake_texture(self,context)
return {'FINISHED'}
class GTT_LightmapEmissionOperator(bpy.types.Operator):
"""Connect baked Lightmap to Emission input of Principled Shader"""
bl_idname = "object.lightmap_to_emission"
bl_label = "Lightmap to Emission"
connect : bpy.props.BoolProperty()
def execute(self, context):
visibility_functions.lightmap_to_emission(self,context,self.connect)
return {'FINISHED'}
class GTT_PreviewLightmap(bpy.types.Operator):
"""Connect baked Lightmap to Base Color input of Principled Shader"""
bl_idname = "object.preview_lightmap"
bl_label = "Lightmap to Base Color"
connect : bpy.props.BoolProperty()
def execute(self, context):
context.scene.texture_settings.preview_lightmap = self.connect
visibility_functions.preview_lightmap(self,context)
return {'FINISHED'}
# ----------------------- UV OPERATORS--------------------#
class GTT_AddUVOperator(bpy.types.Operator):
"""Add uv layer with layer name entered above"""
bl_idname = "object.add_uv"
bl_label = "Add UV to all selected objects"
uv_name:bpy.props.StringProperty()
def execute(self, context):
sel_objects = context.selected_objects
for obj in sel_objects:
if obj.type != "MESH":
continue
uv_layers = obj.data.uv_layers
if self.uv_name in uv_layers:
print("UV Name already take, choose another one")
continue
uv_layers.new(name=self.uv_name)
uv_layers.get(self.uv_name).active = True
return {'FINISHED'}
class GTT_RemoveUVOperator(bpy.types.Operator):
"""Delete all uv layers found in uv_slot entered above"""
bl_idname = "object.remove_uv"
bl_label = "Remove UV"
uv_slot: bpy.props.IntProperty()
def execute(self, context):
sel_objects = context.selected_objects
self.uv_slot -= 1
for obj in sel_objects:
if obj.type != "MESH":
continue
uv_layers = obj.data.uv_layers
if len(uv_layers) > self.uv_slot:
uv_layers.remove(uv_layers[self.uv_slot])
return {'FINISHED'}
class GTT_SetActiveUVOperator(bpy.types.Operator):
"""Set the acive uv to the slot entered above"""
bl_idname = "object.set_active_uv"
bl_label = "Set Active UV"
uv_slot:bpy.props.IntProperty()
def execute(self, context):
sel_objects = context.selected_objects
self.uv_slot -= 1
for obj in sel_objects:
if obj.type != "MESH":
continue
uv_layers = obj.data.uv_layers
if len(uv_layers) > self.uv_slot:
uv_layers.active_index = self.uv_slot
return {'FINISHED'}
# ----------------------- CLEAN OPERATORS--------------------#
# class CleanBakesOperator(bpy.types.Operator):
class GTT_RemoveLightmapOperator(bpy.types.Operator):
"""Remove Lightmap and UV Node"""
bl_idname = "material.clean_lightmap"
bl_label = "Clean Lightmap"
def execute(self, context):
selected_objects = context.selected_objects
bake_settings = context.scene.bake_settings
all_materials = set()
slots_array = [obj.material_slots for obj in selected_objects]
for slots in slots_array:
for slot in slots:
all_materials.add(slot.material)
for mat in all_materials:
if mat is None:
continue
node_functions.remove_node(mat,bake_settings.texture_node_lightmap)
node_functions.remove_node(mat,"Mulitply Lightmap")
node_functions.remove_node(mat,"Second_UV")
#remove lightmap flag
for obj in selected_objects:
obj.hasLightmap = False
if obj.get('lightmap_name') is not None :
del obj["lightmap_name"]
return {'FINISHED'}
class GTT_RemoveAOOperator(bpy.types.Operator):
"""Remove AO Node and clear baking flags on object"""
bl_idname = "material.clean_ao_map"
bl_label = "Clean AO map"
def execute(self, context):
# visibility_functions.switch_baked_material(False,"scene")
bpy.ops.material.clean_materials()
bake_settings = context.scene.bake_settings
selected_objects = context.selected_objects
all_materials = set()
slots_array = [obj.material_slots for obj in selected_objects]
for slots in slots_array:
for slot in slots:
all_materials.add(slot.material)
for mat in all_materials:
if mat is None:
continue
node_functions.remove_node(mat,bake_settings.texture_node_ao)
node_functions.remove_node(mat,"glTF Settings")
#remove flag
for obj in selected_objects:
if obj.get('ao_map_name') is not None :
del obj["ao_map_name"]
if obj.get('bake_version') is not None :
del obj["bake_version"]
return {'FINISHED'}
class GTT_CleanTexturesOperator(bpy.types.Operator):
"""Remove unreferenced images"""
bl_idname = "image.clean_textures"
bl_label = "Clean Textures"
def execute(self, context):
for image in bpy.data.images:
if not image.users or list(image.size) == [0,0]:
bpy.data.images.remove(image)
return {'FINISHED'}
class GTT_CleanMaterialsOperator(bpy.types.Operator):
"""Clean materials with no users and remove empty material slots"""
bl_idname = "material.clean_materials"
bl_label = "Clean Materials"
def execute(self, context):
material_functions.clean_empty_materials()
material_functions.clean_no_user_materials()
material_functions.use_nodes()
return {'FINISHED'}
class GTT_CleanUnusedImagesOperator(bpy.types.Operator):
"""Clean all images from Hard Disk that are not used in scene"""
bl_idname = "scene.clean_unused_images"
bl_label = "Clean Images"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
return True
def execute(self, context):
images_in_folder = []
images_in_blender = bpy.data.images
image_paths_in_blender = []
filePath = bpy.data.filepath
path = os.path.dirname(filePath) + "\\textures"
# find images on hard drive
if os.path.exists(path):
for path, subdirs, files in os.walk(path):
for name in files:
images_in_folder.append(path+"\\"+name)
for img in images_in_blender:
image_paths_in_blender.append(img.filepath)
images_intersection = basic_functions.Intersection(image_paths_in_blender,images_in_folder)
images_to_clean = basic_functions.Diff(images_in_folder,images_intersection)
print("Deleting files :")
for img in images_to_clean:
os.remove(img)
print(img)
return {'FINISHED'}
# ----------------------- FILE OPERATORS--------------------#
class GTT_OpenTexturesFolderOperator(bpy.types.Operator):
"""Open Texture folder if it exists, bake or scale texture to create texture folder"""
bl_idname = "scene.open_textures_folder"
bl_label = "Open Folder"
texture_path="\\textures\\"
@classmethod
def poll(self, context):
filePath = bpy.data.filepath
path = os.path.dirname(filePath)
if os.path.exists(path + self.texture_path):
return True
return False
def execute(self, context):
filepath = bpy.data.filepath
directory = os.path.dirname(filepath) + self.texture_path
if filepath != "":
subprocess.call("explorer " + directory, shell=True)
else:
self.report({'INFO'}, 'You need to save Blend file first !')
return {"FINISHED"}
class GOVIE_Open_Link_Operator(bpy.types.Operator):
bl_idname = "scene.open_link"
bl_label = "Open Website"
bl_description = "Go to GOVIE Website"
url : bpy.props.StringProperty(name="url")
@classmethod
def poll(cls, context):
return True
def execute(self, context):
bpy.ops.wm.url_open(url=self.url)
return {"FINISHED"}
@@ -0,0 +1,260 @@
import bpy
from .. Functions import gui_functions
# from .. Update import addon_updater_ops
class GTT_ResolutionPanel(bpy.types.Panel):
bl_idname = "GLBTEXTOOLS_PT_resolution_panel"
bl_label = "Global Settings"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = 'GLB Texture Tools'
# bl_parent_id = "TEXTURETOOLS_PT_parent_panel"
bl_order = 0
def draw(self, context):
layout = self.layout
scene = context.scene
box = layout.box()
column = box.column()
column.prop(scene, "img_bake_size")
column.prop(scene,"img_file_format")
column.prop(scene,"affect")
column.prop(scene.cycles,"device")
class GTT_BakeTexturePanel(bpy.types.Panel):
bl_idname = "GLBTEXTOOLS_PT_bake_panel"
bl_label = "Baking"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = 'GLB Texture Tools'
bl_order = 1
def draw(self, context):
layout = self.layout
scene = context.scene
data = bpy.data
bake_settings = bpy.context.scene.bake_settings
row = layout.row()
row.prop(bake_settings, 'open_bake_settings_menu', text="Bake Settings", icon = 'TRIA_DOWN' if bake_settings.open_bake_settings_menu else 'TRIA_RIGHT' )
if bake_settings.open_bake_settings_menu:
box = layout.box()
col = box.column(align = True)
row = col.row(align = True)
row.prop(scene.bake_settings, 'pbr_bake', text="PBR",toggle = True)
row.prop(scene.bake_settings, 'pbr_samples', text="Samples",toggle = True)
row = col.row(align = True)
row.prop(scene.bake_settings, 'ao_bake', text="AO",toggle = True)
row.prop(scene.bake_settings, 'ao_samples', text="Samples")
row = col.row(align = True)
row.prop(scene.bake_settings, 'lightmap_bake', text="Lightmap",toggle = True)
row.prop(scene.bake_settings, 'lightmap_samples', text="Samples")
if bake_settings.pbr_bake:
row = box.row()
# col = row.collumn()
row.prop(scene.bake_settings, 'mute_texture_nodes', text="Mute Texture Mapping")
# row.prop(scene.bake_settings, 'bake_image_clear', text="Clear Bake Image")
if bake_settings.lightmap_bake or bake_settings.ao_bake:
row = box.row()
row.prop(scene.bake_settings, 'bake_image_name', text="")
row = box.row()
row.prop(scene.bake_settings, 'baking_groups',text="")
row.operator("object.select_lightmap_objects",text="",icon="RESTRICT_SELECT_OFF")
if bake_settings.lightmap_bake:
try:
box.prop(scene.world.node_tree.nodes["Background"].inputs[1],'default_value',text="World Influence")
except:
pass
if bake_settings.ao_bake:
box.prop(scene.world.light_settings,"distance",text="AO Distance")
box.prop(scene.bake_settings, 'unwrap_margin', text="UV Margin")
box.prop(scene.bake_settings, 'bake_margin', text="Bake Margin")
split = box.split()
col = split.column(align=True)
col.prop(scene.bake_settings, 'unwrap', text="Unwrap")
col.prop(scene.bake_settings, 'bake_image_clear', text="Clear Bake Image")
col = split.column(align=True)
col.prop(scene.bake_settings, 'denoise', text="Denoise")
if bake_settings.denoise:
col.prop(scene.bake_settings, 'show_texture_after_bake', text="Show Texture after Bake")
# LIGHTMAPPED OBJECT LIST
# row = layout.row()
# row.prop(scene.bake_settings, 'open_object_bake_list_menu', text="Lightmapped Objects", icon = 'TRIA_DOWN' if bake_settings.open_object_bake_list_menu else 'TRIA_RIGHT' )
# BAKE LIST
if bake_settings.open_object_bake_list_menu:
layout.template_list("GTT_BAKE_IMAGE_UL_List", "", data, "objects", scene.bake_settings, "bake_object_index")
row = layout.row(align=True)
row.scale_y = 2.0
row.operator("object.gtt_bake_operator",text="Bake Textures")
row.operator("scene.open_textures_folder",icon='FILEBROWSER')
class GTT_VisibilityPanel(bpy.types.Panel):
bl_idname = "GLBTEXTOOLS_PT_visibility_panel"
bl_label = "Visibility"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = 'GLB Texture Tools'
bl_order = 2
def draw(self, context):
layout = self.layout
scene = context.scene
col = layout.column()
row = col.row()
row.operator("object.switch_org_mat_operator",icon = 'NODE_MATERIAL', text="Show Original Material")
row.operator("object.switch_bake_mat_operator",icon = 'MATERIAL', text="Show Baked Material")
# row = col.row(align=True)
layout.prop(scene.texture_settings,"preview_lightmap",text="Show without Lightmap" if scene.texture_settings.preview_lightmap else "Preview Lightmap on Material", icon="SHADING_RENDERED" if scene.texture_settings.preview_bake_texture else "NODE_MATERIAL")
layout.prop(scene.texture_settings,"preview_bake_texture", text="Show Material" if scene.texture_settings.preview_bake_texture else "Preview Baked Texture", icon="SHADING_RENDERED" if scene.texture_settings.preview_bake_texture else "NODE_MATERIAL")
class GTT_CleanupPanel(bpy.types.Panel):
bl_idname = "GLBTEXTOOLS_PT_cleanup_panel"
bl_label = "Cleanup"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = 'GLB Texture Tools'
bl_order = 3
def draw(self, context):
layout = self.layout
row = layout.row()
row.operator("image.clean_textures",text="Clean Textures",icon = 'OUTLINER_OB_IMAGE')
row.operator("material.clean_materials",text="Clean Materials",icon = 'NODE_MATERIAL')
row = layout.row()
row.operator("material.clean_lightmap",text="Clean Lightmap",icon = 'MOD_UVPROJECT')
row.operator("material.clean_ao_map",text="Clean AO Map",icon = 'TRASH')
row = layout.row()
# row.operator("scene.clean_unused_images",text="Clean Unused Images",icon = 'TRASH')
class GTT_TextureSelectionPanel(bpy.types.Panel):
bl_idname = "GLBTEXTOOLS_PT_tex_selection_panel"
bl_label = "Texture"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = 'GLB Texture Tools'
# bl_parent_id = "TEXTURETOOLS_PT_parent_panel"
bl_order = 4
def draw(self, context):
layout = self.layout
scene = context.scene
data = bpy.data
texture_settings = bpy.context.scene.texture_settings
# UI LIST
gui_functions.headline(layout,(0.6,"IMAGE NAME"),(0.5,"SIZE"),(1,"KB"))
layout.template_list("GTT_TEX_UL_List", "", data, "images", scene.texture_settings, "texture_index")
row = layout.row()
row.prop(texture_settings, 'open_texture_settings_menu', text="Texture Settings", icon = 'TRIA_DOWN' if texture_settings.open_texture_settings_menu else 'TRIA_RIGHT' )
if texture_settings.open_texture_settings_menu:
box = layout.box()
box.prop(scene.texture_settings, 'show_all_textures', text="Show all Textures")
box.prop(scene.texture_settings, 'show_per_material', text="Show Textures per Material")
box.prop(scene.texture_settings, 'operate_on_all_textures', text="Operate on all Textures in List")
box.label(text="If scaled images don't get saved/exported, try unpacking before scaling !")
row = box.row()
row.operator("file.unpack_all",text="Unpack")
row.operator("file.pack_all",text="Pack")
# Select Material by Texture
row = layout.row()
row.prop(scene.texture_settings,"open_sel_mat_menu",text="Select Material by Texture", icon = 'TRIA_DOWN' if scene.texture_settings.open_sel_mat_menu else 'TRIA_RIGHT' )
if scene.texture_settings.open_sel_mat_menu:
box = layout.box()
col = box.column(align = True)
col.operator("scene.select_mat_by_tex",text="Select Material",icon='RESTRICT_SELECT_OFF')
col = box.column(align = True)
if len(scene.materials_found) > 0:
col.label(text="Texture found in Material :")
for mat in scene.materials_found:
col.label(text=mat)
else:
col.label(text="Texture not used")
# Scale and Clean
row = layout.row()
row.scale_y = 2.0
row.operator("image.scale_image",text="Scale Image",icon= 'FULLSCREEN_EXIT')
class GTT_UVPanel(bpy.types.Panel):
bl_idname = "GLBTEXTOOLS_PT_UV_panel"
bl_label = "UV"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "GLB Texture Tools"
bl_options = {'DEFAULT_CLOSED'}
bl_order = 5
def draw(self, context):
layout = self.layout
uv_settings = context.scene.uv_settings
box = layout.box()
row = box.row()
row.prop(uv_settings,"uv_name", text="UV Name")
row.prop(uv_settings,"uv_slot", text="UV Slot")
row = box.row()
row.operator("object.add_uv",text="Add UV",icon = 'ADD').uv_name = uv_settings.uv_name
row.operator("object.remove_uv",text="Remove UV",icon = 'REMOVE').uv_slot = uv_settings.uv_slot
row.operator("object.set_active_uv",text="Set Active",icon = 'RESTRICT_SELECT_OFF').uv_slot = uv_settings.uv_slot
class GTT_HelpPanel(bpy.types.Panel):
bl_idname = "GLBTEXTOOLS_PT_help_panel"
bl_label = "Help"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "GLB Texture Tools"
bl_order = 6
def draw(self,context):
layout = self.layout
lang = bpy.app.translations.locale
if lang == 'en_US':
layout.label(text="Find out how to use this addon")
layout.operator("scene.open_link",text="Add-on Documentation",icon='HELP').url = "https://govie.de/en/tutorials-blender/?utm_source=blender-add-on&utm_medium=button#glb_texture_tools"
if lang == 'de_DE':
layout.label(text="Hilfe zur Bedienung des Add-ons")
layout.operator("scene.open_link",text="Add-on Documentation",icon='HELP').url = "https://govie.de/tutorials-blender/?utm_source=blender-add-on&utm_medium=button#glb_texture_tools"
@@ -0,0 +1,88 @@
import bpy
from .. Functions import node_functions
from .. Functions import image_functions
from .. Functions import constants
class GTT_TEX_UL_List(bpy.types.UIList):
image_name_list = set()
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
if self.layout_type in {'DEFAULT', 'COMPACT'}:
row = layout.row()
split = row.split(factor=0.6)
split.label(text=str(item.users) + " " +item.name)
split = split.split(factor=0.5)
split.label(text=str(item.size[0]))
split = split.split(factor=1)
filepath = item.filepath
if filepath != '':
filesize = image_functions.get_file_size(filepath)
split.label(text=str(filesize).split('.')[0])
else:
split.label(text="file not saved")
def filter_items(self, context, data, propname):
texture_settings = context.scene.texture_settings
selected_objects = context.selected_objects
all_materials = set()
all_image_names = [image.name for image in data.images]
slots_array = [obj.material_slots for obj in selected_objects]
for slots in slots_array:
for slot in slots:
all_materials.add(slot.material)
# Default return values.
flt_flags = []
flt_neworder = []
images = []
if texture_settings.show_all_textures:
images = [image.name for image in data.images if image.name not in ('Viewer Node','Render Result')]
if texture_settings.show_per_material:
mat = context.active_object.active_material
nodes = mat.node_tree.nodes
tex_nodes = node_functions.get_nodes_by_type(nodes,constants.Node_Types.image_texture)
[images.append(node.image.name) for node in tex_nodes]
else:
for mat in all_materials:
if mat is None:
continue
nodes = mat.node_tree.nodes
tex_nodes = node_functions.get_nodes_by_type(nodes,constants.Node_Types.image_texture)
[images.append(node.image.name) for node in tex_nodes]
# save filtered list to texture settings / ui_list_itmes
self.image_name_list.clear()
for img in images:
self.image_name_list.add(img)
flt_flags = [self.bitflag_filter_item if name in images else 0 for name in all_image_names]
return flt_flags, flt_neworder
class GTT_BAKE_IMAGE_UL_List(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
if self.layout_type in {'DEFAULT', 'COMPACT'}:
row = layout.row()
row.label(text=item.name)
def filter_items(self, context, data, propname):
bake_settings = context.scene.bake_settings
objects = data.objects
# Default return values.
flt_flags = []
flt_neworder = []
flt_flags = [self.bitflag_filter_item if obj.lightmap_name == bake_settings.baking_groups and obj.hasLightmap else 0 for obj in objects]
return flt_flags, flt_neworder
@@ -0,0 +1,150 @@
import bpy
from bpy import context
from bpy.props import *
from .. Functions import gui_functions
from .. Functions import visibility_functions
bpy.types.Scene.img_bake_size = EnumProperty(
name='Image Size',
description='Set resolution for baking and scaling images. This effects PBR, AO and Lightmap baking as well as scaling imges. As of scaling images, choose ORIGINAL to switch back to your image before scaling.',
default='1024',
items=[
('128', '128', 'Set image size to 128'),
('256', '256', 'Set image size to 256'),
('512', '512', 'Set image size to 512'),
('1024', '1024', 'Set image size to 1024'),
('2048', '2048', 'Set image size to 2048'),
('4096', '4096', 'Set image size to 4096'),
('8184', '8184', 'Set image size to 8184'),
('0', 'Original', 'Set image back to original file'),
])
bpy.types.Scene.img_file_format = EnumProperty(
name='File Format',
description='Set file format for output image',
default='JPEG',
items=[
('JPEG', 'JPEG', 'JPG is a lossy format with no additional alpha channel, use for color maps'),
('PNG', 'PNG', 'PNG is lossless and has option for alpha channel, use for normal maps'),
('HDR', 'HDR', 'HDR is a 32 bit format, use if you need more details or see color banding'),
])
bpy.types.Scene.affect = EnumProperty(
name='Affect',
description='Define if operator should run on active material, all materials on selected object, all materials on visible objects or all materials in scene.',
default='linked',
items=[
('active', 'ACTIVE', 'Change only active materials'),
('selected', 'SELECTED', 'Change all selected materials'),
('linked', 'LINKED MATERIALS', 'Change all materials on selected objects and all objects that share these materials'),
('visible', 'VISIBLE', 'Change all visible materials'),
('scene', 'SCENE', 'Change all materials in scene'),
])
class GTT_UV_Settings(bpy.types.PropertyGroup):
uv_slot: IntProperty(default=1)
uv_name: StringProperty(default="AO")
bpy.utils.register_class(GTT_UV_Settings)
bpy.types.Scene.uv_settings = PointerProperty(type=GTT_UV_Settings)
class GTT_Cleanup_Settings(bpy.types.PropertyGroup):
clean_texture: BoolProperty(default=True)
clean_material: BoolProperty(default=True)
clean_bake: BoolProperty(default=False)
clean_node_tree: BoolProperty(default=False)
bpy.utils.register_class(GTT_Cleanup_Settings)
bpy.types.Scene.cleanup_settings = PointerProperty(type=GTT_Cleanup_Settings)
class GTT_Bake_Settings(bpy.types.PropertyGroup):
open_bake_settings_menu: BoolProperty(default = False)
open_object_bake_list_menu: BoolProperty(default = False)
# Type of bake
pbr_bake: BoolProperty(default = True,update=gui_functions.update_pbr_button)
pbr_samples: IntProperty(name = "Samples for PBR bake", default = 1)
ao_bake: BoolProperty(default = False,update=gui_functions.update_ao_button)
ao_samples: IntProperty(name = "Samples for AO bake", default = 2)
lightmap_bake: BoolProperty(default = False,update=gui_functions.update_lightmap_button)
lightmap_samples: IntProperty(name = "Samples for Lightmap bake", default = 10)
baking_groups: EnumProperty(
name='Baked Textures',
description='Groups of objects that share the same baking maps. Click on cursor on the right to select all objectes in that group.',
items=gui_functions.update_bake_list
)
def get_current_bake_type(self):
if self.pbr_bake:
current_bake_type = "pbr"
if self.ao_bake:
current_bake_type = "ao"
if self.lightmap_bake:
current_bake_type = "lightmap"
return current_bake_type
def get_baked_lightmaps(context):
return gui_functions.update_bake_list(context,context)
# render_pass : EnumProperty(name='Render Pass',description='Define Render Pass',items=[("Combined","Combined","Bake all passes in this singel Combined Pass"),("Lightmap","Lightmap","Lightmap")])
# Checkbox settings
bake_image_name: StringProperty(default="Lightmap")
bake_image_clear: BoolProperty(default= True)
mute_texture_nodes: BoolProperty(default = True)
bake_margin:IntProperty(default=2)
unwrap_margin:FloatProperty(default=0.002)
unwrap: BoolProperty(default= True)
denoise: BoolProperty(default=False)
show_texture_after_bake: BoolProperty(default=True)
bake_object_index:IntProperty(name = "Index for baked Objects", default = 0)
uv_name="Lightmap"
texture_node_lightmap="Lightmap"
texture_node_ao="AO"
cleanup_textures=False
bpy.utils.register_class(GTT_Bake_Settings)
bpy.types.Scene.bake_settings = PointerProperty(type=GTT_Bake_Settings)
class GTT_Texture_Settings(bpy.types.PropertyGroup):
open_texture_settings_menu:BoolProperty(default=False)
open_sel_mat_menu:BoolProperty(default=False)
show_all_textures:BoolProperty(default=False)
show_per_material:BoolProperty(default=False)
operate_on_all_textures:BoolProperty(default=False)
preview_bake_texture:BoolProperty(default=False,update=visibility_functions.preview_bake_texture)
preview_lightmap:BoolProperty(default=False,update=visibility_functions.preview_lightmap)
texture_index:IntProperty(name = "Index for Texture List", default=0, update=visibility_functions.update_selected_image)
bpy.utils.register_class(GTT_Texture_Settings)
bpy.types.Scene.texture_settings = PointerProperty(type=GTT_Texture_Settings)
# HELP PANEL PROPERTIES
def run_help_operator(self,context):
bpy.ops.scene.help('INVOKE_DEFAULT')
bpy.types.Scene.help_tex_tools = BoolProperty(default=False,update=run_help_operator)
# MATERIAL PROPERTIES
bpy.types.Material.bake_material_name = StringProperty()
# IMAGE PROPERTIES
bpy.types.Image.org_filepath = StringProperty()
bpy.types.Image.org_image_name = StringProperty()
# OBJECT PROPERTIES
bpy.types.Object.hasLightmap = BoolProperty()
bpy.types.Object.lightmap_name = StringProperty()
bpy.types.Object.ao_map_name = StringProperty()
bpy.types.Object.bake_version = StringProperty()
+3
View File
@@ -0,0 +1,3 @@
Documentation can be found here and a help
https://docs.google.com/document/d/1w1h3ySyZG4taG01RbbDsPODsUP06cRCggxQKCt32QTk/edit
@@ -0,0 +1,34 @@
import bpy
from . import functions
from .. Functions import material_functions
def Diff(li1, li2):
return (list(set(li1) - set(li2)))
def compareImages(images):
firstImg = images[0]
firstImgPixels = firstImg.pixels
for img in images[1:]:
currentImgPixels = img.pixels
diff = Diff(firstImgPixels, currentImgPixels)
if len(diff) == 0:
print(firstImg.name + " and " + img.name + " map !")
return compareImages(images[1:])
class CleanDupliTexturesOperator(bpy.types.Operator):
"""By checking the incoming links in the PBR Shader, new Textures are generated that will include all the node transformations."""
bl_idname = "object.clean_dupli_textures"
bl_label = "Delete Texture Duplicates"
def execute(self, context):
# images = bpy.data.images
# compareImages(images)
material_functions.clean_empty_materials(self)
return {'FINISHED'}
@@ -0,0 +1,50 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
bl_info = {
"name": "GLBTextureTools",
"author": "Lorenz Wieseke",
"description": "",
"blender": (4, 0, 0),
"version": (0,1,5),
"location": "3DView > Properties (N -KEY) > GLB Texture Tools",
"warning": "",
"wiki_url": "https://govie-editor.de/help/glb-texture-tools/",
"tracker_url": "https://github.com/LorenzWieseke/GLBTextureTools/issues",
"category": "Generic"
}
import bpy
from .Functions import gui_functions
from .Functions import image_functions
from . import auto_load
auto_load.init()
classes = auto_load.init()
def register():
auto_load.register()
bpy.app.handlers.depsgraph_update_post.clear()
bpy.app.handlers.depsgraph_update_post.append(gui_functions.update_on_selection)
bpy.app.handlers.load_post.append(gui_functions.init_values)
bpy.app.handlers.save_pre.append(image_functions.save_images)
def unregister():
auto_load.unregister()
bpy.app.handlers.depsgraph_update_post.clear()
bpy.app.handlers.load_post.clear()
bpy.app.handlers.save_pre.clear()
@@ -0,0 +1 @@
theme: jekyll-theme-cayman
+157
View File
@@ -0,0 +1,157 @@
import os
import bpy
import sys
import typing
import inspect
import pkgutil
import importlib
from pathlib import Path
__all__ = (
"init",
"register",
"unregister",
)
blender_version = bpy.app.version
modules = None
ordered_classes = None
def init():
global modules
global ordered_classes
modules = get_all_submodules(Path(__file__).parent)
ordered_classes = get_ordered_classes_to_register(modules)
def register():
for cls in ordered_classes:
bpy.utils.register_class(cls)
for module in modules:
if module.__name__ == __name__:
continue
if hasattr(module, "register"):
module.register()
def unregister():
for cls in reversed(ordered_classes):
bpy.utils.unregister_class(cls)
for module in modules:
if module.__name__ == __name__:
continue
if hasattr(module, "unregister"):
module.unregister()
# Import modules
#################################################
def get_all_submodules(directory):
return list(iter_submodules(directory, directory.name))
def iter_submodules(path, package_name):
for name in sorted(iter_submodule_names(path)):
yield importlib.import_module("." + name, package_name)
def iter_submodule_names(path, root=""):
for _, module_name, is_package in pkgutil.iter_modules([str(path)]):
if is_package:
sub_path = path / module_name
sub_root = root + module_name + "."
yield from iter_submodule_names(sub_path, sub_root)
else:
yield root + module_name
# Find classes to register
#################################################
def get_ordered_classes_to_register(modules):
return toposort(get_register_deps_dict(modules))
def get_register_deps_dict(modules):
my_classes = set(iter_my_classes(modules))
my_classes_by_idname = {cls.bl_idname : cls for cls in my_classes if hasattr(cls, "bl_idname")}
deps_dict = {}
for cls in my_classes:
deps_dict[cls] = set(iter_my_register_deps(cls, my_classes, my_classes_by_idname))
return deps_dict
def iter_my_register_deps(cls, my_classes, my_classes_by_idname):
yield from iter_my_deps_from_annotations(cls, my_classes)
yield from iter_my_deps_from_parent_id(cls, my_classes_by_idname)
def iter_my_deps_from_annotations(cls, my_classes):
for value in typing.get_type_hints(cls, {}, {}).values():
dependency = get_dependency_from_annotation(value)
if dependency is not None:
if dependency in my_classes:
yield dependency
def get_dependency_from_annotation(value):
if blender_version >= (2, 93):
if isinstance(value, bpy.props._PropertyDeferred):
return value.keywords.get("type")
else:
if isinstance(value, tuple) and len(value) == 2:
if value[0] in (bpy.props.PointerProperty, bpy.props.CollectionProperty):
return value[1]["type"]
return None
def iter_my_deps_from_parent_id(cls, my_classes_by_idname):
if bpy.types.Panel in cls.__bases__:
parent_idname = getattr(cls, "bl_parent_id", None)
if parent_idname is not None:
parent_cls = my_classes_by_idname.get(parent_idname)
if parent_cls is not None:
yield parent_cls
def iter_my_classes(modules):
base_types = get_register_base_types()
for cls in get_classes_in_modules(modules):
if any(base in base_types for base in cls.__bases__):
if not getattr(cls, "is_registered", False):
yield cls
def get_classes_in_modules(modules):
classes = set()
for module in modules:
for cls in iter_classes_in_module(module):
classes.add(cls)
return classes
def iter_classes_in_module(module):
for value in module.__dict__.values():
if inspect.isclass(value):
yield value
def get_register_base_types():
return set(getattr(bpy.types, name) for name in [
"Panel", "Operator", "PropertyGroup",
"AddonPreferences", "Header", "Menu",
"Node", "NodeSocket", "NodeTree",
"UIList", "RenderEngine",
"Gizmo", "GizmoGroup",
])
# Find order to register to solve dependencies
#################################################
def toposort(deps_dict):
sorted_list = []
sorted_values = set()
while len(deps_dict) > 0:
unsorted = []
for value, deps in deps_dict.items():
if len(deps) == 0:
sorted_list.append(value)
sorted_values.add(value)
else:
unsorted.append(value)
deps_dict = {value : deps_dict[value] - sorted_values for value in unsorted}
return sorted_list
+6
View File
@@ -0,0 +1,6 @@
use uv-packmaster if installed
change camera size back to what is was
reconstruct node tree in compositor
pbr texture bake -> add same uv to bake plane to be able to bake procedural textures or ! add uv automaticly