2025-07-01
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
from mathutils import Vector
|
||||
from math import pi
|
||||
|
||||
from . import utilities_color
|
||||
from . import settings
|
||||
|
||||
|
||||
keywords_low = ['lowpoly','low','lowp','lp','lo'] #excluded 'l' since TexTools v1.4
|
||||
keywords_high = ['highpoly','high','highp','hp','hi'] #excluded 'h' since TexTools v1.4
|
||||
keywords_cage = ['cage'] #excluded 'c' since TexTools v1.4
|
||||
keywords_float = ['floater','float'] #excluded 'f' since TexTools v1.4
|
||||
|
||||
split_chars = [' ','_','.','-']
|
||||
|
||||
if settings.bversion >= 4.0:
|
||||
chs = {'ech':26, 'rch':2, 'trch':2, 'ssch':7, 'scch':0, 'mch':1, 'sch':12, 'stch':13, 'ach':14, 'arch':15, 'shch':23, 'shtch':25, 'cch':18, 'crch':19, 'esch':27, 'alch':4}
|
||||
else:
|
||||
sh = 0 # shift of channels, depends on the Blender version
|
||||
if settings.bversion >= 3.0:
|
||||
sh = 2
|
||||
chs = {'ech':17+sh, 'rch':7+sh, 'ssch':1, 'scch':3, 'mch':4+sh, 'sch':5+sh, 'stch':0, 'shtch':0, 'ach':8+sh, 'arch':9+sh, 'shch':10+sh, 'cch':12+sh, 'crch':13+sh, 'trch':16+sh, 'esch':18+sh, 'alch':19+sh}
|
||||
|
||||
allMaterials = []
|
||||
allMaterialsNames = []
|
||||
elementsCount = 0
|
||||
|
||||
|
||||
class BakeMode:
|
||||
material = "" #Material name from external blend file
|
||||
type = 'EMIT'
|
||||
normal_space = 'TANGENT'
|
||||
setVColor = None #Set Vertex color method
|
||||
color = (0.23, 0.23, 0.23, 1) #Background color
|
||||
engine = 'CYCLES' #render engine, by default CYCLES
|
||||
composite = None #use composite scene to process end result
|
||||
use_project = False #Bake projected?
|
||||
invert = False
|
||||
relink = {'needed':False}
|
||||
params = [] #UI Parameters from scene settings
|
||||
|
||||
def __init__(self, material="", type='EMIT', normal_space='TANGENT', setVColor=None, color= (0.23, 0.23, 0.23, 1), engine='CYCLES', params = [], composite=None, use_project=False, invert=False, relink = {'needed':False}):
|
||||
self.material = material
|
||||
self.type = type
|
||||
self.normal_space = normal_space
|
||||
self.setVColor = setVColor
|
||||
self.color = color
|
||||
self.engine = engine
|
||||
self.params = params
|
||||
self.composite = composite
|
||||
self.use_project = use_project
|
||||
self.invert = invert
|
||||
self.relink = relink
|
||||
|
||||
|
||||
|
||||
def on_select_bake_mode(mode):
|
||||
print("Mode changed {}".format(mode))
|
||||
|
||||
if len(settings.sets) > 0:
|
||||
name_texture = "{}_{}".format(settings.sets[0].name, mode)
|
||||
|
||||
if name_texture in bpy.data.images:
|
||||
image = bpy.data.images[name_texture]
|
||||
|
||||
# Set background image
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.ui_type == 'UV':
|
||||
area.spaces[0].image = image
|
||||
|
||||
|
||||
def store_bake_settings():
|
||||
# Render Settings
|
||||
settings.bake_render_engine = bpy.context.scene.render.engine
|
||||
settings.bake_cycles_device = bpy.context.scene.cycles.device
|
||||
settings.bake_cycles_samples = bpy.context.scene.cycles.samples
|
||||
if settings.bversion >= 2.92:
|
||||
settings.bake_target_mode = bpy.context.scene.render.bake.target
|
||||
if settings.bversion < 3:
|
||||
settings.use_progressive_refine = bpy.context.scene.cycles.use_progressive_refine
|
||||
if settings.bversion >= 3:
|
||||
settings.use_denoising = bpy.context.scene.cycles.use_denoising
|
||||
|
||||
# Disable Objects that are meant to be hidden
|
||||
sets = settings.sets
|
||||
objects_sets = []
|
||||
for bset in sets:
|
||||
for obj in bset.objects_low:
|
||||
if obj not in objects_sets:
|
||||
objects_sets.append(obj)
|
||||
for obj in bset.objects_high:
|
||||
if obj not in objects_sets:
|
||||
objects_sets.append(obj)
|
||||
for obj in bset.objects_cage:
|
||||
if obj not in objects_sets:
|
||||
objects_sets.append(obj)
|
||||
|
||||
settings.bake_objects_hide_render = []
|
||||
|
||||
# for obj in bpy.context.view_layer.objects:
|
||||
# if obj.hide_render == False and obj not in objects_sets:
|
||||
# Check if layer is active:
|
||||
# for l in range(0, len(obj.layers)):
|
||||
# if obj.layers[l] and bpy.context.scene.layers[l]:
|
||||
# settings.bake_objects_hide_render.append(obj)
|
||||
# break #sav
|
||||
|
||||
for obj in settings.bake_objects_hide_render:
|
||||
obj.hide_render = True
|
||||
# obj.cycles_visibility.shadow = False
|
||||
|
||||
|
||||
|
||||
def restore_bake_settings():
|
||||
# Render Settings
|
||||
if settings.bake_render_engine != '':
|
||||
bpy.context.scene.render.engine = settings.bake_render_engine
|
||||
|
||||
bpy.context.scene.cycles.device = settings.bake_cycles_device
|
||||
bpy.context.scene.cycles.samples = settings.bake_cycles_samples
|
||||
|
||||
if settings.bversion >= 2.92:
|
||||
bpy.context.scene.render.bake.target = settings.bake_target_mode
|
||||
if settings.bversion < 3:
|
||||
bpy.context.scene.cycles.use_progressive_refine = settings.use_progressive_refine
|
||||
if settings.bversion >= 3:
|
||||
bpy.context.scene.cycles.use_denoising = settings.use_denoising
|
||||
|
||||
# Restore Objects that were hidden for baking
|
||||
for obj in settings.bake_objects_hide_render:
|
||||
if obj:
|
||||
obj.hide_render = False
|
||||
# obj.cycles_visibility.shadow = True
|
||||
|
||||
|
||||
|
||||
def get_set_name_base(obj):
|
||||
|
||||
def remove_digits(name):
|
||||
# Remove blender naming digits, e.g. cube.001, cube.002,...
|
||||
if len(name) > 4 and name[-4] == '.' and name[-3:].isdigit():
|
||||
return name[:-4]
|
||||
return name
|
||||
|
||||
# Reference parent as base name
|
||||
if obj.parent and obj.parent in bpy.context.selected_objects:
|
||||
return remove_digits(obj.parent.name).lower()
|
||||
|
||||
# Reference group name as base name
|
||||
elif len(obj.users_collection) == 2:
|
||||
return remove_digits(obj.users_collection[0].name).lower()
|
||||
|
||||
# Use Object name
|
||||
else:
|
||||
return remove_digits(obj.name).lower()
|
||||
|
||||
|
||||
|
||||
def get_set_name(obj):
|
||||
|
||||
if bpy.context.scene.texToolsSettings.bake_force == "Multi":
|
||||
return obj.name
|
||||
|
||||
# Get Basic name
|
||||
name = get_set_name_base(obj)
|
||||
|
||||
# Split by ' ','_','.' etc.
|
||||
split = name.lower()
|
||||
for char in split_chars:
|
||||
split = split.replace(char,' ')
|
||||
strings = split.split(' ')
|
||||
|
||||
# Remove all keys from name
|
||||
keys = keywords_cage + keywords_high + keywords_low + keywords_float
|
||||
new_strings = []
|
||||
for string in strings:
|
||||
is_found = False
|
||||
for key in keys:
|
||||
if string == key:
|
||||
is_found = True
|
||||
break
|
||||
if not is_found:
|
||||
new_strings.append(string)
|
||||
elif len(new_strings) > 0:
|
||||
# No more strings once key is found if we have already something
|
||||
break
|
||||
|
||||
return "_".join(new_strings)
|
||||
|
||||
|
||||
|
||||
def get_object_type(obj):
|
||||
|
||||
if bpy.context.scene.texToolsSettings.bake_force == "Multi":
|
||||
return 'low'
|
||||
|
||||
name = get_set_name_base(obj)
|
||||
|
||||
# Detect by name pattern
|
||||
split = name.lower()
|
||||
for char in split_chars:
|
||||
split = split.replace(char,' ')
|
||||
strings = split.split(' ')
|
||||
|
||||
# Detect float, more rare than low
|
||||
for string in strings:
|
||||
for key in keywords_float:
|
||||
if key == string:
|
||||
return 'float'
|
||||
|
||||
# Detect by modifiers (Only if more than 1 object selected)
|
||||
if bpy.context.preferences.addons[__package__].preferences.bool_modifier_auto_high:
|
||||
if len(bpy.context.selected_objects) > 1:
|
||||
if obj.modifiers:
|
||||
for modifier in obj.modifiers:
|
||||
if modifier.type == 'SUBSURF' and modifier.render_levels > 0:
|
||||
return 'high'
|
||||
elif modifier.type == 'BEVEL':
|
||||
return 'high'
|
||||
|
||||
# Detect High first, more rare
|
||||
for string in strings:
|
||||
for key in keywords_high:
|
||||
if key == string:
|
||||
return 'high'
|
||||
|
||||
# Detect cage, more rare than low
|
||||
for string in strings:
|
||||
for key in keywords_cage:
|
||||
if key == string:
|
||||
return 'cage'
|
||||
|
||||
# Detect low
|
||||
for string in strings:
|
||||
for key in keywords_low:
|
||||
if key == string:
|
||||
return 'low'
|
||||
|
||||
# if nothing was detected, assume it is low
|
||||
return 'low'
|
||||
|
||||
|
||||
|
||||
def get_baked_images(sets):
|
||||
images = []
|
||||
for bset in sets:
|
||||
name_texture = "{}_".format(bset.name)
|
||||
for image in bpy.data.images:
|
||||
if name_texture in image.name:
|
||||
images.append(image)
|
||||
|
||||
return images
|
||||
|
||||
|
||||
|
||||
def get_bake_sets():
|
||||
filtered = {}
|
||||
if len(bpy.context.selected_objects) == 0:
|
||||
if bpy.context.active_object is not None:
|
||||
if bpy.context.active_object.mode == 'EDIT' and bpy.context.active_object.type == 'MESH':
|
||||
filtered[bpy.context.active_object] = get_object_type(bpy.context.active_object)
|
||||
else:
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj.type == 'MESH':
|
||||
filtered[obj] = get_object_type(obj)
|
||||
|
||||
groups = []
|
||||
# Group by names
|
||||
for obj in filtered:
|
||||
name = get_set_name(obj)
|
||||
|
||||
if not groups:
|
||||
groups.append([obj])
|
||||
else:
|
||||
isFound = False
|
||||
for group in groups:
|
||||
groupName = get_set_name(group[0])
|
||||
if name == groupName:
|
||||
group.append(obj)
|
||||
isFound = True
|
||||
break
|
||||
|
||||
if not isFound:
|
||||
groups.append([obj])
|
||||
|
||||
# Sort groups alphabetically
|
||||
keys = [get_set_name(group[0]) for group in groups]
|
||||
keys.sort()
|
||||
sorted_groups = []
|
||||
for key in keys:
|
||||
for group in groups:
|
||||
if key == get_set_name(group[0]):
|
||||
sorted_groups.append(group)
|
||||
break
|
||||
|
||||
groups = sorted_groups
|
||||
|
||||
bake_sets = []
|
||||
for group in groups:
|
||||
low = []
|
||||
high = []
|
||||
cage = []
|
||||
float = []
|
||||
for obj in group:
|
||||
if filtered[obj] == 'low':
|
||||
low.append(obj)
|
||||
elif filtered[obj] == 'high':
|
||||
high.append(obj)
|
||||
elif filtered[obj] == 'cage':
|
||||
cage.append(obj)
|
||||
elif filtered[obj] == 'float':
|
||||
float.append(obj)
|
||||
|
||||
name = get_set_name(group[0])
|
||||
bake_sets.append(BakeSet(name, low, cage, high, float))
|
||||
|
||||
return bake_sets
|
||||
|
||||
|
||||
|
||||
class BakeSet:
|
||||
objects_low = [] #low poly geometry
|
||||
objects_cage = [] #Cage low poly geometry
|
||||
objects_high = [] #High poly geometry
|
||||
objects_float = [] #Floating geometry
|
||||
name = ""
|
||||
|
||||
has_issues = False
|
||||
|
||||
def __init__(self, name, objects_low, objects_cage, objects_high, objects_float):
|
||||
self.objects_low = objects_low
|
||||
self.objects_cage = objects_cage
|
||||
self.objects_high = objects_high
|
||||
self.objects_float = objects_float
|
||||
self.name = name
|
||||
|
||||
# Needs low poly objects to bake onto
|
||||
if len(objects_low) == 0:
|
||||
self.has_issues = True
|
||||
|
||||
# Check Cage Object count to low poly count
|
||||
if len(objects_cage) > 0 and len(objects_low) != len(objects_cage):
|
||||
self.has_issues = True
|
||||
|
||||
# Check for UV maps
|
||||
for obj in objects_low:
|
||||
if len(obj.data.uv_layers) == 0:
|
||||
self.has_issues = True
|
||||
break
|
||||
|
||||
|
||||
|
||||
def assign_vertex_color(obj):
|
||||
if len(obj.data.vertex_colors) > 0 :
|
||||
vclsNames = [vcl.name for vcl in obj.data.vertex_colors]
|
||||
if 'TexTools_temp' in vclsNames:
|
||||
obj.data.vertex_colors['TexTools_temp'].active = True
|
||||
obj.data.vertex_colors['TexTools_temp'].active_render = True
|
||||
else:
|
||||
obj.data.vertex_colors.new(name='TexTools_temp')
|
||||
obj.data.vertex_colors['TexTools_temp'].active = True
|
||||
obj.data.vertex_colors['TexTools_temp'].active_render = True
|
||||
else:
|
||||
obj.data.vertex_colors.new(name='TexTools_temp')
|
||||
obj.data.vertex_colors['TexTools_temp'].active = True
|
||||
obj.data.vertex_colors['TexTools_temp'].active_render = True
|
||||
|
||||
|
||||
|
||||
def setup_vertex_color_selection(obj):
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
obj.select_set( state = True, view_layer = None)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
|
||||
bpy.ops.object.mode_set(mode='VERTEX_PAINT')
|
||||
|
||||
bpy.context.tool_settings.vertex_paint.brush.color = (0, 0, 0)
|
||||
bpy.context.object.data.use_paint_mask = False
|
||||
bpy.ops.paint.vertex_color_set()
|
||||
|
||||
bpy.context.tool_settings.vertex_paint.brush.color = (1, 1, 1)
|
||||
bpy.context.object.data.use_paint_mask = True
|
||||
bpy.ops.paint.vertex_color_set()
|
||||
bpy.context.object.data.use_paint_mask = False
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
|
||||
|
||||
def setup_vertex_color_dirty(obj):
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
obj.select_set( state = True, view_layer = None)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
# Fill white then,
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if settings.bversion >= 3.4:
|
||||
colorLayerIndex = obj.data.attributes.active_color_index
|
||||
colorLayer = bm.loops.layers.color[colorLayerIndex]
|
||||
else:
|
||||
colorLayer = bm.loops.layers.color.active
|
||||
|
||||
color = utilities_color.safe_color( (1, 1, 1) )
|
||||
|
||||
for face in bm.faces:
|
||||
for loop in face.loops:
|
||||
loop[colorLayer] = color
|
||||
|
||||
obj.data.update()
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
bpy.ops.paint.vertex_color_dirt(dirt_angle=pi/2)
|
||||
bpy.ops.paint.vertex_color_dirt()
|
||||
|
||||
|
||||
|
||||
def setup_vertex_color_id_material(obj, previous_materials):
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
obj.select_set( state = True, view_layer = None)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
|
||||
|
||||
# bm = bmesh.from_edit_mesh(obj.data)
|
||||
# colorLayer = bm.loops.layers.color.active
|
||||
|
||||
for i, mtlname in enumerate(previous_materials[obj]):
|
||||
if mtlname is not None:
|
||||
# Select related faces
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
|
||||
bm = bmesh.from_edit_mesh(bpy.context.active_object.data)
|
||||
for face in bm.faces:
|
||||
if face.material_index == i:
|
||||
face.select = True
|
||||
|
||||
color = utilities_color.get_color_id(allMaterials.index(bpy.data.materials[mtlname]), 256, jitter=True)
|
||||
|
||||
bpy.ops.object.mode_set(mode='VERTEX_PAINT')
|
||||
bpy.context.tool_settings.vertex_paint.brush.color = color
|
||||
bpy.context.object.data.use_paint_mask = True
|
||||
bpy.ops.paint.vertex_color_set()
|
||||
|
||||
obj.data.update()
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
|
||||
|
||||
def setup_vertex_color_id_element(obj):
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
obj.select_set( state = True, view_layer = None)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_mode(use_extend=False, use_expand=False, type='FACE')
|
||||
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
if settings.bversion >= 3.4:
|
||||
colorLayerIndex = obj.data.attributes.active_color_index
|
||||
colorLayer = bm.loops.layers.color[colorLayerIndex]
|
||||
else:
|
||||
colorLayer = bm.loops.layers.color.active
|
||||
|
||||
|
||||
# Collect elements
|
||||
processed = set([])
|
||||
groups = []
|
||||
for face in bm.faces:
|
||||
|
||||
if face not in processed:
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
face.select = True
|
||||
bpy.ops.mesh.select_linked(delimit={'NORMAL'})
|
||||
linked = [face for face in bm.faces if face.select]
|
||||
|
||||
for link in linked:
|
||||
processed.add(link)
|
||||
groups.append(linked)
|
||||
|
||||
global elementsCount
|
||||
|
||||
# Color each group
|
||||
for i in range(0,len(groups)):
|
||||
color = utilities_color.get_color_id(elementsCount + i, 256, jitter=True)
|
||||
color = utilities_color.safe_color( color )
|
||||
for face in groups[i]:
|
||||
for loop in face.loops:
|
||||
loop[colorLayer] = color
|
||||
|
||||
elementsCount += len(groups)
|
||||
|
||||
obj.data.update()
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
|
||||
|
||||
def get_image_material(image):
|
||||
|
||||
# Clear & Create new material
|
||||
material = None
|
||||
if image.name in bpy.data.materials:
|
||||
# Incorrect existing material, delete first and create new for cycles
|
||||
material = bpy.data.materials[image.name]
|
||||
bpy.data.materials.remove(material, do_unlink=True)
|
||||
material = bpy.data.materials.new(image.name)
|
||||
else:
|
||||
material = bpy.data.materials.new(image.name)
|
||||
|
||||
|
||||
# Cycles Material
|
||||
if bpy.context.scene.render.engine == 'CYCLES' or bpy.context.scene.render.engine == 'BLENDER_EEVEE':
|
||||
material.use_nodes = True
|
||||
|
||||
# Image Node
|
||||
node_image = None
|
||||
if "image" in material.node_tree.nodes:
|
||||
node_image = material.node_tree.nodes["image"]
|
||||
else:
|
||||
node_image = material.node_tree.nodes.new("ShaderNodeTexImage")
|
||||
node_image.name = "image"
|
||||
node_image.select = True
|
||||
node_image.image = image
|
||||
material.node_tree.nodes.active = node_image
|
||||
|
||||
#Base Diffuse BSDF
|
||||
bsdf_node = None
|
||||
for n in material.node_tree.nodes:
|
||||
if n.bl_idname == "ShaderNodeBsdfPrincipled":
|
||||
bsdf_node = n
|
||||
|
||||
if "_normal_" in image.name:
|
||||
# Add Normal Map Nodes
|
||||
node_normal_map = None
|
||||
if "normal_map" in material.node_tree.nodes:
|
||||
node_normal_map = material.node_tree.nodes["normal_map"]
|
||||
else:
|
||||
node_normal_map = material.node_tree.nodes.new("ShaderNodeNormalMap")
|
||||
node_normal_map.name = "normal_map"
|
||||
|
||||
# Tangent or World space
|
||||
if(image.name.endswith("normal_tangent")):
|
||||
node_normal_map.space = 'TANGENT'
|
||||
elif(image.name.endswith("normal_object")):
|
||||
node_normal_map.space = 'WORLD'
|
||||
|
||||
# image to normal_map link
|
||||
material.node_tree.links.new(node_image.outputs[0], node_normal_map.inputs[1])
|
||||
|
||||
# normal_map to diffuse_bsdf link
|
||||
if settings.bversion < 2.91:
|
||||
material.node_tree.links.new(node_normal_map.outputs[0], bsdf_node.inputs[19])
|
||||
else:
|
||||
material.node_tree.links.new(node_normal_map.outputs[0], bsdf_node.inputs[20])
|
||||
|
||||
node_normal_map.location = bsdf_node.location - Vector((200, 0))
|
||||
node_image.location = node_normal_map.location - Vector((200, 0))
|
||||
|
||||
else:
|
||||
# Other images display as Color
|
||||
# dump(node_image.color_mapping.bl_rna.property_tags)
|
||||
|
||||
# image node to diffuse node link
|
||||
material.node_tree.links.new(node_image.outputs[0], bsdf_node.inputs[0])
|
||||
|
||||
return material
|
||||
|
||||
elif bpy.context.scene.render.engine == 'BLENDER_EEVEE':
|
||||
material.use_nodes = True
|
||||
|
||||
texture = None
|
||||
if image.name in bpy.data.textures:
|
||||
texture = bpy.data.textures[image.name]
|
||||
else:
|
||||
texture = bpy.data.textures.new(image.name, 'IMAGE')
|
||||
|
||||
texture.image = image
|
||||
slot = material.texture_slot.add()
|
||||
slot.texture = texture
|
||||
slot.mapping = 'FLAT'
|
||||
|
||||
# return material
|
||||
Reference in New Issue
Block a user