2025-12-01
This commit is contained in:
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user