1104 lines
44 KiB
Python
1104 lines
44 KiB
Python
import bpy
|
|
import os
|
|
import time
|
|
|
|
from . import utilities_ui
|
|
from . import utilities_uv
|
|
from . import settings
|
|
from . import utilities_bake as ub
|
|
|
|
|
|
# Notes: https://docs.blender.org/manual/en/dev/render/blender_render/bake.html
|
|
modes={
|
|
#'displacement': ub.BakeMode('', type='DISPLACEMENT', use_project=True, color=(0, 0, 0, 1), engine='CYCLES'),
|
|
'normal_tangent': ub.BakeMode('', type='NORMAL', color=(0.5, 0.5, 1, 1), use_project=True),
|
|
'normal_object': ub.BakeMode('', type='NORMAL', color=(0.5, 0.5, 1, 1), normal_space='OBJECT'),
|
|
'bevel_mask': ub.BakeMode('bake_bevel_mask', type='EMIT', color=(0, 0, 0, 1), params=["bake_bevel_samples","bake_bevel_size"]),
|
|
'normal_tangent_bevel': ub.BakeMode('bake_bevel_normal', type='NORMAL', color=(0.5, 0.5, 1, 1), params=["bake_bevel_samples","bake_bevel_size"]),
|
|
'normal_object_bevel': ub.BakeMode('bake_bevel_normal', type='NORMAL', color=(0.5, 0.5, 1, 1), normal_space='OBJECT', params=["bake_bevel_samples","bake_bevel_size"]),
|
|
'thickness': ub.BakeMode('bake_thickness', type='EMIT', color=(0, 0, 0, 1), params=["bake_samples","bake_thickness_distance","bake_thickness_contrast","bake_thickness_local"]),
|
|
'cavity': ub.BakeMode('bake_cavity', type='EMIT', setVColor=ub.setup_vertex_color_dirty),
|
|
'paint_base': ub.BakeMode('bake_paint_base', type='EMIT'),
|
|
'dust': ub.BakeMode('bake_dust', type='EMIT', setVColor=ub.setup_vertex_color_dirty),
|
|
'ao': ub.BakeMode('', type='AO', params=["bake_samples"], engine='CYCLES'),
|
|
'position': ub.BakeMode('bake_position', type='EMIT'),
|
|
'curvature': ub.BakeMode('', type='NORMAL', use_project=True, params=["bake_curvature_size"], composite="curvature"),
|
|
'wireframe': ub.BakeMode('bake_wireframe', type='EMIT', color=(0, 0, 0, 1), params=["bake_wireframe_size"]),
|
|
'id_element': ub.BakeMode('bake_vertex_color', type='EMIT', setVColor=ub.setup_vertex_color_id_element),
|
|
'id_material': ub.BakeMode('bake_vertex_color', type='EMIT', setVColor=ub.setup_vertex_color_id_material),
|
|
'selection': ub.BakeMode('bake_vertex_color', type='EMIT', color=(0, 0, 0, 1), setVColor=ub.setup_vertex_color_selection),
|
|
'diffuse': ub.BakeMode('', type='DIFFUSE'),
|
|
'base_color': ub.BakeMode('', type='EMIT', relink = {'needed':True, 'b':ub.chs['ech'], 'n':0}),
|
|
'sss_strength': ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['ssch']}),
|
|
'sss_color': ub.BakeMode('', type='EMIT', relink = {'needed':True, 'b':ub.chs['ech'], 'n':ub.chs['scch']}),
|
|
'metallic': ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['mch']}),
|
|
'specular': ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['sch']}),
|
|
'specular_tint': ub.BakeMode('', type='EMIT', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['ech'], 'n':ub.chs['stch']}),
|
|
'roughness': ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1)),
|
|
'glossiness': ub.BakeMode('', type='ROUGHNESS', color=(1, 1, 1, 1), invert=True),
|
|
'anisotropic': ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['ach']}),
|
|
'anisotropic_rotation': ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['arch']}),
|
|
'sheen': ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['shch']}),
|
|
'sheen_tint': ub.BakeMode('', type='EMIT', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['ech'], 'n':ub.chs['shtch']}),
|
|
'clearcoat': ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['cch']}),
|
|
'clearcoat_roughness': ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['crch']}),
|
|
'transmission': ub.BakeMode('', type='TRANSMISSION'),
|
|
'transmission_roughness': ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['trch']}),
|
|
'emission': ub.BakeMode('', type='EMIT', color=(0, 0, 0, 1)),
|
|
'environment': ub.BakeMode('', type='ENVIRONMENT'),
|
|
'uv': ub.BakeMode('', type='UV'),
|
|
'shadow': ub.BakeMode('', type='SHADOW', params=["bake_samples"]),
|
|
'combined': ub.BakeMode('', type='COMBINED', params=["bake_samples"])
|
|
}
|
|
|
|
if settings.bversion >= 2.91:
|
|
modes['emission_strength']= ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['esch']})
|
|
modes['alpha']= ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['alch']})
|
|
else:
|
|
modes['alpha']= ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':ub.chs['rch'], 'n':ub.chs['esch']})
|
|
|
|
|
|
|
|
class op(bpy.types.Operator):
|
|
bl_idname = "uv.textools_bake"
|
|
bl_label = "Bake"
|
|
bl_description = "Bake selected objects"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
|
|
if len(settings.sets) == 0:
|
|
settings.bake_error = ""
|
|
return False
|
|
|
|
bake_mode = utilities_ui.get_bake_mode()
|
|
if bake_mode not in modes:
|
|
settings.bake_error = ""
|
|
return False
|
|
|
|
if bake_mode in {'ao','normal_tangent','normal_object','curvature','environment','uv','shadow'}:
|
|
settings.bake_error = ""
|
|
return True
|
|
|
|
if bake_mode == 'combined':
|
|
if (not bpy.context.scene.render.bake.use_pass_direct) and (not bpy.context.scene.render.bake.use_pass_indirect) and (not bpy.context.scene.render.bake.use_pass_emit):
|
|
settings.bake_error = "Lighting or Emit needed"
|
|
return False
|
|
settings.bake_error = ""
|
|
return True
|
|
|
|
if modes[bake_mode].setVColor or not modes[bake_mode].material:
|
|
def is_bakeable(obj):
|
|
if len(obj.data.materials) <= 0: # There are no material slots
|
|
settings.bake_error = "Materials needed"
|
|
return False
|
|
elif not any(obj.data.materials): # All material slots are empty
|
|
settings.bake_error = "Materials needed"
|
|
return False
|
|
else:
|
|
for slot in obj.material_slots:
|
|
if slot.material is not None:
|
|
if slot.material.use_nodes == False:
|
|
settings.bake_error = "Nodal materials needed"
|
|
return False
|
|
bsdf_node = None
|
|
for n in slot.material.node_tree.nodes:
|
|
if n.bl_idname == "ShaderNodeBsdfPrincipled":
|
|
bsdf_node = n
|
|
if not bsdf_node:
|
|
bool_alpha_ignore = bpy.context.preferences.addons[__package__].preferences.bool_alpha_ignore
|
|
bool_clean_transmission = bpy.context.preferences.addons[__package__].preferences.bool_clean_transmission
|
|
builtin_modes_material = {'diffuse','emission','roughness','glossiness','transmission'}
|
|
if modes[bake_mode].relink['needed'] or (bool_clean_transmission and bake_mode == 'transmission') or (bool_alpha_ignore and bake_mode not in builtin_modes_material):
|
|
settings.bake_error = "BSDF nodes needed"
|
|
return False
|
|
# else:
|
|
# settings.bake_error = "Materials needed"
|
|
# return False
|
|
settings.bake_error = ""
|
|
return True
|
|
|
|
def is_vc_ready(obj):
|
|
if len(obj.data.vertex_colors) > 7:
|
|
settings.bake_error = "An empty VC layer needed"
|
|
return False
|
|
settings.bake_error = ""
|
|
return True
|
|
|
|
for bset in settings.sets:
|
|
if (len(bset.objects_high) + len(bset.objects_float)) == 0:
|
|
if not modes[bake_mode].material:
|
|
for obj in bset.objects_low:
|
|
if not is_bakeable(obj):
|
|
return False
|
|
if modes[bake_mode].setVColor:
|
|
for obj in bset.objects_low:
|
|
if not is_vc_ready(obj):
|
|
return False
|
|
else:
|
|
if not modes[bake_mode].material:
|
|
for obj in (bset.objects_high + bset.objects_float):
|
|
if not is_bakeable(obj):
|
|
return False
|
|
if modes[bake_mode].setVColor:
|
|
for obj in (bset.objects_high + bset.objects_float):
|
|
if not is_vc_ready(obj):
|
|
return False
|
|
|
|
settings.bake_error = ""
|
|
return True
|
|
|
|
|
|
def execute(self, context):
|
|
startTime = time.monotonic()
|
|
preferences = bpy.context.preferences.addons[__package__].preferences
|
|
circular_report = [False, ]
|
|
color_report = [False, ]
|
|
|
|
if preferences.bool_clean_transmission:
|
|
modes['transmission']= ub.BakeMode('', type='ROUGHNESS', color=(0, 0, 0, 1), relink = {'needed':True, 'b':7, 'n':15})
|
|
else:
|
|
modes['transmission']= ub.BakeMode('', type='TRANSMISSION')
|
|
|
|
bake_mode = utilities_ui.get_bake_mode()
|
|
|
|
if bake_mode not in modes:
|
|
self.report({'ERROR_INVALID_INPUT'}, "Unknown mode '{}' only available: '{}'".format(bake_mode, ", ".join(modes.keys() )) )
|
|
return {'CANCELLED'}
|
|
|
|
# Store Selection
|
|
selected_objects = [obj for obj in bpy.context.selected_objects]
|
|
active_object = bpy.context.view_layer.objects.active
|
|
pre_selection_mode = None
|
|
if active_object:
|
|
pre_selection_mode = bpy.context.active_object.mode
|
|
ub.store_bake_settings()
|
|
|
|
if preferences.bake_device != 'DEFAULT':
|
|
bpy.context.scene.cycles.device = preferences.bake_device
|
|
bpy.context.scene.render.engine = modes[bake_mode].engine #Switch render engine
|
|
|
|
# Avoid weird rendering problems when Progressive Refine is activated from Blender 2.90
|
|
if settings.bversion < 3:
|
|
bpy.context.scene.cycles.use_progressive_refine = False
|
|
# Make it sure that an Image, and not a Vertex Colors layer, is the target of the bake
|
|
if settings.bversion >= 2.92:
|
|
bpy.context.scene.render.bake.target = 'IMAGE_TEXTURES'
|
|
# Disable denoising until it is properly implemented for baking
|
|
if settings.bversion >= 3:
|
|
bpy.context.scene.cycles.use_denoising = False
|
|
|
|
# Render sets
|
|
bake(
|
|
self = self,
|
|
mode = bake_mode,
|
|
size = bpy.context.scene.texToolsSettings.size,
|
|
|
|
bake_force = bpy.context.scene.texToolsSettings.bake_force,
|
|
sampling_scale = int(bpy.context.scene.texToolsSettings.bake_sampling),
|
|
samples = bpy.context.scene.texToolsSettings.bake_samples,
|
|
cage_extrusion = bpy.context.scene.texToolsSettings.bake_cage_extrusion,
|
|
ray_distance = bpy.context.scene.texToolsSettings.bake_ray_distance,
|
|
circular_report = circular_report,
|
|
color_report = color_report,
|
|
selected = selected_objects,
|
|
active = active_object,
|
|
pre_selection_mode = pre_selection_mode
|
|
)
|
|
|
|
elapsed = round(time.monotonic()-startTime, 2)
|
|
if circular_report[0]:
|
|
if color_report[0]:
|
|
self.report({'WARNING'}, "Possible Circular Dependency: a previously baked image may have affected the new bake; " + color_report[0] + "Baking finished in " + str(elapsed) + "s.")
|
|
else:
|
|
self.report({'WARNING'}, "Possible Circular Dependency: a previously baked image may have affected the new bake. Baking finished in " + str(elapsed) + "s.")
|
|
else:
|
|
if color_report[0]:
|
|
self.report({'WARNING'}, color_report[0] + ". Baking finished in " + str(elapsed) + "s.")
|
|
else:
|
|
self.report({'INFO'}, "Baking finished in " + str(elapsed) + "s.")
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
def bake(self, mode, size, bake_force, sampling_scale, samples, cage_extrusion, ray_distance, circular_report, color_report, selected, active, pre_selection_mode):
|
|
print("Bake '{}'".format(mode))
|
|
|
|
# Get the baking sets / pairs
|
|
sets = settings.sets
|
|
|
|
for bset in sets:
|
|
# Requires 1+ low poly objects
|
|
if len(bset.objects_low) == 0:
|
|
self.report({'ERROR_INVALID_INPUT'}, "No low poly object as part of the '{}' set".format(bset.name) )
|
|
return {'CANCELLED'}
|
|
# Check for UV maps
|
|
for obj in bset.objects_low:
|
|
if (not obj.data.uv_layers) or len(obj.data.uv_layers) == 0:
|
|
self.report({'ERROR_INVALID_INPUT'}, "No UV map available for '{}'".format(obj.name))
|
|
return {'CANCELLED'}
|
|
# Check for cage inconsistencies
|
|
if len(bset.objects_cage) > 0 and (len(bset.objects_low) != len(bset.objects_cage)):
|
|
self.report({'ERROR_INVALID_INPUT'}, "{}x cage objects do not match {}x low poly objects for '{}'".format(len(bset.objects_cage), len(bset.objects_low), obj.name))
|
|
return {'CANCELLED'}
|
|
|
|
# Disable edit mode
|
|
if bpy.context.view_layer.objects.active != None and bpy.context.object.mode != 'OBJECT':
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
bool_emission_strength_ignore = bpy.context.preferences.addons[__package__].preferences.bool_emission_ignore
|
|
bool_alpha_ignore = bpy.context.preferences.addons[__package__].preferences.bool_alpha_ignore
|
|
render_width = sampling_scale * size[0]
|
|
render_height = sampling_scale * size[1]
|
|
|
|
|
|
# Get custom materials
|
|
material_loaded = get_material(mode)
|
|
|
|
# Setup properties of the custom material_loaded
|
|
if material_loaded is not None:
|
|
setup_material_loaded(mode, material_loaded)
|
|
|
|
|
|
# If baking Material ID, make sure the color for each material is consistent between bakes
|
|
if mode == 'id_material':
|
|
# Try to redirect deleted materials which were recovered with undo
|
|
if len(ub.allMaterials) > 0 :
|
|
for i, mtl in enumerate(ub.allMaterials):
|
|
try: mtl.name
|
|
except: ub.allMaterials[i] = bpy.data.materials.get(ub.allMaterialsNames[i])
|
|
else: # Store a persistent ordered list of all originally used materials in the scene
|
|
ub.allMaterials = [mtl for mtl in bpy.data.materials if (mtl is not None and mtl.users != 0)]
|
|
ub.allMaterialsNames = [mtl.name for mtl in ub.allMaterials]
|
|
|
|
# If baking Element ID, make sure the color for each element is consistent between bakes
|
|
if mode == 'id_element':
|
|
ub.elementsCount = 0
|
|
|
|
# Create dictionaries to remember original and temporary -copied- materials used in the baked objects
|
|
previous_materials = {}
|
|
copied_materials = {}
|
|
# Container to save existing UDIM tile names of each set
|
|
tiles = []
|
|
|
|
for bset in sets:
|
|
for obj in (bset.objects_low + bset.objects_high + bset.objects_float):
|
|
if obj not in previous_materials:
|
|
previous_materials[obj] = []
|
|
for i, mtl in enumerate(obj.data.materials):
|
|
if mtl is None:
|
|
previous_materials[obj].append(None)
|
|
else:
|
|
previous_materials[obj].append(obj.data.materials[i].name)
|
|
|
|
# Substitute original materials for copied ones to avoid restoral after tuning
|
|
def use_copied_mtls(obj):
|
|
for i, mtl in enumerate(obj.data.materials):
|
|
if mtl is not None:
|
|
if mtl not in copied_materials:
|
|
mat_copied = mtl.copy()
|
|
obj.data.materials[i] = mat_copied
|
|
copied_materials[mtl.name] = mat_copied.name
|
|
else:
|
|
obj.data.materials[i] = copied_materials[mtl.name]
|
|
|
|
def use_material_loaded(obj):
|
|
if len(obj.data.materials) > 0:
|
|
for i in range(len(obj.data.materials)):
|
|
obj.data.materials[i] = bpy.data.materials[material_loaded]
|
|
else:
|
|
obj.data.materials.append(bpy.data.materials[material_loaded])
|
|
|
|
for bset in settings.sets:
|
|
if (len(bset.objects_high) + len(bset.objects_float)) == 0:
|
|
tiles.append( utilities_uv.get_UDIM_tiles( bset.objects_low ) )
|
|
for obj in bset.objects_low:
|
|
if material_loaded is None:
|
|
use_copied_mtls(obj)
|
|
else:
|
|
use_material_loaded(obj)
|
|
else:
|
|
tiles.append( utilities_uv.get_UDIM_tiles( bset.objects_high + bset.objects_float ) )
|
|
if material_loaded is None:
|
|
for obj in (bset.objects_low + bset.objects_high + bset.objects_float):
|
|
use_copied_mtls(obj)
|
|
else:
|
|
for obj in bset.objects_low:
|
|
use_copied_mtls(obj)
|
|
for obj in (bset.objects_high+bset.objects_float):
|
|
use_material_loaded(obj)
|
|
|
|
|
|
relinkedMaterials = []
|
|
EmissionIgnoredMaterials = []
|
|
AlphaIgnoredMaterials = []
|
|
|
|
bakeReadyMaterials = [] # Store references of materials where the baking image node is ready and an Avoid Circular Dependency action has been taken
|
|
image = previous_image = imagecopy = None # Store image references globally just in case they have to be used to bake all sets
|
|
stored_images = [] # [image, previous_image, imagecopy] list of lists
|
|
|
|
|
|
try:
|
|
for s,bset in enumerate(sets):
|
|
|
|
# Get image name
|
|
name_texture = "{}_{}".format(bset.name, mode)
|
|
if bake_force == "Single":
|
|
name_texture = "{}_{}".format(sets[0].name, mode) # In Single mode, bake into the same texture
|
|
#path = bpy.path.abspath("//{}.tga".format(name_texture))
|
|
|
|
is_clear = (not bake_force == "Single") or (bake_force == "Single" and s==0)
|
|
|
|
# Setup "image" to bake on and retrieve "previous_image": an image that exists in the blend file with the same name than "image", maybe used in materials involved in the bake
|
|
if is_clear:
|
|
bakeReadyMaterials = []
|
|
loaded = True
|
|
if material_loaded is None:
|
|
loaded = False
|
|
|
|
image, previous_image = setup_image(color_report, mode, name_texture, render_width, render_height, tiles[s], material_load=loaded)
|
|
|
|
# Avoid Circular Dependency method A: Create image copy to use in existing nodes that may be affected if baking directly in a "previous_image" whose source is an external file
|
|
if image == previous_image:
|
|
imagecopy = image.copy()
|
|
else:
|
|
imagecopy = None
|
|
|
|
image_name = image.name
|
|
|
|
if previous_image is None:
|
|
previous_image_name = None
|
|
else:
|
|
previous_image_name = previous_image.name
|
|
|
|
if imagecopy is None:
|
|
imagecopy_name = None
|
|
else:
|
|
imagecopy_name = imagecopy.name
|
|
|
|
stored_images.append([image_name, previous_image_name, imagecopy_name, name_texture])
|
|
|
|
|
|
|
|
def assign_tune_materials(obj, setup_bake_nodes=False):
|
|
|
|
if material_loaded is not None:
|
|
# If baking ID Materials, update the persistent ordered list of all materials in the scene
|
|
if mode == 'id_material':
|
|
for mtlname in previous_materials[obj]:
|
|
if mtlname is not None:
|
|
if bpy.data.materials[mtlname] not in ub.allMaterials:
|
|
ub.allMaterials.append(bpy.data.materials[mtlname])
|
|
ub.allMaterialsNames.append(mtlname)
|
|
if modes[mode].setVColor:
|
|
ub.assign_vertex_color(obj)
|
|
if mode == 'id_material':
|
|
modes[mode].setVColor(obj, previous_materials)
|
|
else:
|
|
modes[mode].setVColor(obj)
|
|
|
|
elif modes[mode].relink['needed']:
|
|
for slot in obj.material_slots:
|
|
if slot.material:
|
|
if slot.material not in relinkedMaterials:
|
|
relink_nodes(mode, slot.material)
|
|
relinkedMaterials.append(slot.material)
|
|
if modes[mode].type == 'EMIT' and settings.bversion >= 2.91:
|
|
if slot.material not in EmissionIgnoredMaterials:
|
|
channel_ignore(modes['emission_strength'].relink['n'], slot.material)
|
|
EmissionIgnoredMaterials.append(slot.material)
|
|
if (bool_alpha_ignore and mode != 'ao' and mode != 'diffuse') or mode == 'alpha':
|
|
if slot.material not in AlphaIgnoredMaterials:
|
|
channel_ignore(modes['alpha'].relink['n'], slot.material)
|
|
AlphaIgnoredMaterials.append(slot.material)
|
|
if setup_bake_nodes:
|
|
setup_image_bake_node(obj, bakeReadyMaterials, image_name, previous_image_name, imagecopy_name)
|
|
|
|
elif bool_emission_strength_ignore and settings.bversion >= 2.91 and mode == 'emission':
|
|
for slot in obj.material_slots:
|
|
if slot.material:
|
|
if slot.material.use_nodes:
|
|
bsdf_node = None
|
|
for n in slot.material.node_tree.nodes:
|
|
if n.bl_idname == "ShaderNodeBsdfPrincipled":
|
|
bsdf_node = n
|
|
if bsdf_node:
|
|
if slot.material not in EmissionIgnoredMaterials:
|
|
channel_ignore(modes['emission_strength'].relink['n'], slot.material)
|
|
EmissionIgnoredMaterials.append(slot.material)
|
|
if (bool_alpha_ignore and mode != 'ao' and mode != 'diffuse') or mode == 'alpha':
|
|
if slot.material not in AlphaIgnoredMaterials:
|
|
channel_ignore(modes['alpha'].relink['n'], slot.material)
|
|
AlphaIgnoredMaterials.append(slot.material)
|
|
if setup_bake_nodes:
|
|
setup_image_bake_node(obj, bakeReadyMaterials, image_name, previous_image_name, imagecopy_name)
|
|
|
|
else:
|
|
if (bool_alpha_ignore and mode != 'ao' and mode != 'diffuse') or mode == 'alpha':
|
|
for slot in obj.material_slots:
|
|
if slot.material:
|
|
if slot.material.use_nodes:
|
|
bsdf_node = None
|
|
for n in slot.material.node_tree.nodes:
|
|
if n.bl_idname == "ShaderNodeBsdfPrincipled":
|
|
bsdf_node = n
|
|
if bsdf_node:
|
|
if slot.material not in AlphaIgnoredMaterials:
|
|
channel_ignore(modes['alpha'].relink['n'], slot.material)
|
|
AlphaIgnoredMaterials.append(slot.material)
|
|
if setup_bake_nodes:
|
|
setup_image_bake_node(obj, bakeReadyMaterials, image_name, previous_image_name, imagecopy_name)
|
|
|
|
|
|
# Assign Materials to Objects / tune the existing materials, and distribute temp bake image nodes
|
|
if (len(bset.objects_high) + len(bset.objects_float)) == 0:
|
|
# Low poly bake: Assign material to lowpoly or tune the existing material/s
|
|
for obj in bset.objects_low:
|
|
if mode in {'ao','normal_tangent','normal_object','curvature','environment','uv','shadow','combined'}:
|
|
# Clean unused material slots?
|
|
# if len(obj.data.materials) > 0:
|
|
# if not any(obj.data.materials): # All material slots are empty
|
|
# obj.active_material_index = 0
|
|
# for i in range(len(obj.material_slots)):
|
|
# bpy.ops.object.material_slot_remove({'object': obj})
|
|
if len(obj.material_slots) == 0 or (not all(obj.data.materials)):
|
|
if "TT_bake_node" not in bpy.data.materials:
|
|
bpy.data.materials.new(name="TT_bake_node")
|
|
if len(obj.material_slots) == 0:
|
|
obj.data.materials.append(bpy.data.materials["TT_bake_node"])
|
|
else:
|
|
for slot in obj.material_slots:
|
|
if not slot.material:
|
|
slot.material = bpy.data.materials["TT_bake_node"]
|
|
assign_tune_materials(obj, setup_bake_nodes=True)
|
|
if material_loaded is not None :
|
|
setup_image_bake_node(bset.objects_low[0], bakeReadyMaterials, image_name, previous_image_name, imagecopy_name)
|
|
else:
|
|
# High to low poly: Low poly requires any material to bake into image
|
|
for obj in bset.objects_low:
|
|
if len(obj.material_slots) == 0 or (not all(obj.data.materials)):
|
|
if "TT_bake_node" not in bpy.data.materials:
|
|
bpy.data.materials.new(name="TT_bake_node")
|
|
if len(obj.material_slots) == 0:
|
|
obj.data.materials.append(bpy.data.materials["TT_bake_node"])
|
|
else:
|
|
for slot in obj.material_slots:
|
|
if not slot.material:
|
|
slot.material = bpy.data.materials["TT_bake_node"]
|
|
setup_image_bake_node(obj, bakeReadyMaterials, image_name, previous_image_name, imagecopy_name)
|
|
# Assign material to highpoly or tune the existing material/s
|
|
for obj in (bset.objects_high+bset.objects_float):
|
|
assign_tune_materials(obj)
|
|
|
|
|
|
|
|
#print("Bake '{}' = {}".format(bset.name, path))
|
|
print("Bake "+bset.name)
|
|
|
|
# Hide all cage objects i nrender
|
|
for obj_cage in bset.objects_cage:
|
|
obj_cage.hide_render = True
|
|
|
|
# Bake each low poly object in this set
|
|
for i in range(len(bset.objects_low)):
|
|
obj_low = bset.objects_low[i]
|
|
obj_cage = None if i >= len(bset.objects_cage) else bset.objects_cage[i]
|
|
|
|
# Disable hide render
|
|
obj_low.hide_render = False
|
|
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
obj_low.select_set( state = True, view_layer = None)
|
|
bpy.context.view_layer.objects.active = obj_low
|
|
|
|
# if modes[mode].engine == 'BLENDER_EEVEE': #TODO would this still be needed when the set background code has been moved to the next lines?
|
|
# # Assign image to texture faces
|
|
# bpy.ops.object.mode_set(mode='EDIT')
|
|
# bpy.ops.mesh.select_all(action='SELECT')
|
|
# for area in bpy.context.screen.areas:
|
|
# if area.ui_type == 'UV':
|
|
# area.spaces[0].image = image
|
|
# # bpy.data.screens['UV Editing'].areas[1].spaces[0].image = image
|
|
# bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
if is_clear and i == 0:
|
|
# Set background image (CYCLES & BLENDER_EEVEE)
|
|
# for area in bpy.context.screen.areas:
|
|
# if area.ui_type == 'UV':
|
|
# area.spaces[0].image = bpy.data.images[image_name]
|
|
# Invert background if final invert of the baked image is needed
|
|
if modes[mode].invert:
|
|
bpy.ops.image.invert(invert_r=True, invert_g=True, invert_b=True, invert_a=False)
|
|
|
|
for obj_high in (bset.objects_high):
|
|
obj_high.select_set( state = True, view_layer = None)
|
|
|
|
cycles_bake(
|
|
mode,
|
|
bpy.context.scene.texToolsSettings.padding,
|
|
sampling_scale,
|
|
samples,
|
|
cage_extrusion,
|
|
ray_distance,
|
|
len(bset.objects_high) > 0,
|
|
obj_cage
|
|
)
|
|
|
|
# Bake Floaters separate bake
|
|
if len(bset.objects_float) > 0:
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
for obj_high in (bset.objects_float):
|
|
obj_high.select_set( state = True, view_layer = None)
|
|
obj_low.select_set( state = True, view_layer = None)
|
|
|
|
cycles_bake(
|
|
mode,
|
|
0,
|
|
sampling_scale,
|
|
samples,
|
|
cage_extrusion,
|
|
ray_distance,
|
|
len(bset.objects_float) > 0,
|
|
obj_cage
|
|
)
|
|
|
|
# Restore renderable for cage objects
|
|
for obj_cage in bset.objects_cage:
|
|
obj_cage.hide_render = False
|
|
|
|
# Operations to be made only after the bake is -or the bakes are- finished
|
|
if (not bake_force == "Single") or (bake_force == "Single" and s == len(sets)-1):
|
|
if modes[mode].invert:
|
|
bpy.ops.image.invert(invert_r=True, invert_g=True, invert_b=True, invert_a=False)
|
|
if render_width != size[0] or render_height != size[1]:
|
|
bpy.data.images[image_name].scale(size[0],size[1])
|
|
|
|
if modes[mode].composite:
|
|
apply_composite(image_name, modes[mode].composite, bpy.context.scene.texToolsSettings.bake_curvature_size)
|
|
|
|
|
|
# TODO: if autosave: image.save()
|
|
|
|
|
|
finally:
|
|
# Restore materials whether or not there is a problem during the baking
|
|
for obj in previous_materials:
|
|
if len(previous_materials[obj]) == 0:
|
|
if len(obj.material_slots) > 0:
|
|
obj.active_material_index = 0
|
|
context_override = {'object': obj}
|
|
for i in range(len(obj.material_slots)):
|
|
with bpy.context.temp_override(**context_override):
|
|
bpy.ops.object.material_slot_remove()
|
|
else:
|
|
for i, mtlname in enumerate(previous_materials[obj]):
|
|
if mtlname is None:
|
|
obj.data.materials[i] = None
|
|
else:
|
|
obj.data.materials[i] = bpy.data.materials[mtlname]
|
|
|
|
if material_loaded is not None:
|
|
if modes[mode].setVColor:
|
|
vclsNames = [vcl.name for vcl in obj.data.vertex_colors]
|
|
if 'TexTools_temp' in vclsNames :
|
|
obj.data.vertex_colors.remove(obj.data.vertex_colors['TexTools_temp'])
|
|
|
|
|
|
for mtl in copied_materials.values():
|
|
bpy.data.materials.remove(bpy.data.materials[mtl], do_unlink=True)
|
|
|
|
if "TT_bake_node" in bpy.data.materials:
|
|
bpy.data.materials.remove(bpy.data.materials["TT_bake_node"], do_unlink=True)
|
|
|
|
if material_loaded is not None:
|
|
bpy.data.materials.remove(bpy.data.materials[material_loaded], do_unlink=True)
|
|
|
|
|
|
for images in stored_images:
|
|
if images[1] and images[1] in bpy.data.images and bpy.data.images[images[0]] != bpy.data.images[images[1]]:
|
|
# If Avoid Circular Dependency method B was used, change previous_image for the newly baked image in all materials
|
|
#bpy.data.images[images[1]].user_remap(bpy.data.images[images[0]])
|
|
for material in bpy.data.materials:
|
|
if material.use_nodes == True:
|
|
tree = material.node_tree
|
|
for node in tree.nodes:
|
|
if node.bl_idname == 'ShaderNodeTexImage':
|
|
if node.image == bpy.data.images[images[1]]:
|
|
if material in copied_materials:
|
|
circular_report[0] = True
|
|
node.image = bpy.data.images[images[0]]
|
|
|
|
# Force previous or temporary images to be removed (user related errors may be prompted in console, but the process should be stable)
|
|
if images[2] and images[2] in bpy.data.images:
|
|
#bpy.data.images[images[2]].user_clear()
|
|
#bpy.data.batch_remove([bpy.data.images[images[2]]])
|
|
bpy.data.images.remove(bpy.data.images[images[2]], do_unlink=True) # Delete imagecopy
|
|
elif images[1] and images[1] in bpy.data.images:
|
|
#bpy.data.images[images[1]].user_clear()
|
|
#bpy.data.batch_remove([bpy.data.images[images[1]]])
|
|
bpy.data.images.remove(bpy.data.images[images[1]], do_unlink=True) # Delete previous_image
|
|
|
|
# If Avoid Circular Dependency method A was used, clear users of the saved image and recover them for the newly baked image (they share the ID but are not the same)
|
|
if images[2]:
|
|
circular_report[0] = True
|
|
bpy.data.images[images[0]].user_clear()
|
|
for material in bpy.data.materials:
|
|
if material.use_nodes == True:
|
|
tree = material.node_tree
|
|
for node in tree.nodes:
|
|
if node.bl_idname == 'ShaderNodeTexImage':
|
|
if node.image.name == images[0]:
|
|
node.image = bpy.data.images[images[0]]
|
|
|
|
# Always set proper name to the newly baked image (when all previous or temporary images have been removed)
|
|
bpy.data.images[images[0]].name = images[3]
|
|
|
|
|
|
# Restore settings and selection mode
|
|
ub.restore_bake_settings()
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
for obj in selected:
|
|
obj.select_set( state = True, view_layer = None)
|
|
# Enter and exit Edit Mode to force set a real vertex colors layer as active
|
|
bpy.ops.object.mode_set(mode='EDIT')
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
if active:
|
|
bpy.context.view_layer.objects.active = active
|
|
bpy.ops.object.mode_set(mode=pre_selection_mode)
|
|
|
|
|
|
|
|
|
|
def apply_composite(image_name, scene_name, size):
|
|
image = bpy.data.images[image_name]
|
|
previous_scene = bpy.context.window.scene
|
|
# avoid Sun Position addon error
|
|
preWorldPropertiesNodesBool = previous_scene.world.use_nodes
|
|
|
|
# Get Scene with compositing nodes
|
|
scene = None
|
|
if scene_name in bpy.data.scenes:
|
|
scene = bpy.data.scenes[scene_name]
|
|
else:
|
|
path = os.path.join(os.path.dirname(__file__), "resources/compositing.blend", "Scene")
|
|
bpy.ops.wm.append(filename=scene_name, directory=path, link=False, autoselect=False)
|
|
scene = bpy.data.scenes[scene_name]
|
|
|
|
if scene:
|
|
# Switch scene
|
|
bpy.context.window.scene = scene
|
|
bpy.context.window.scene.world.use_nodes = preWorldPropertiesNodesBool
|
|
|
|
#Setup composite nodes for Curvature
|
|
if "Image" in scene.node_tree.nodes:
|
|
scene.node_tree.nodes["Image"].image = image
|
|
|
|
if "Offset" in scene.node_tree.nodes:
|
|
scene.node_tree.nodes["Offset"].outputs[0].default_value = size
|
|
|
|
# Render image
|
|
bpy.ops.render.render(use_viewport=False)
|
|
|
|
# Get last images of viewer node and render result
|
|
image_viewer_node = get_last_item("Viewer Node", bpy.data.images)
|
|
image_render_result = get_last_item("Render Result", bpy.data.images)
|
|
|
|
#Copy pixels
|
|
image.pixels = image_viewer_node.pixels[:]
|
|
image.update()
|
|
|
|
if image_viewer_node:
|
|
bpy.data.images.remove(image_viewer_node)
|
|
if image_render_result:
|
|
bpy.data.images.remove(image_render_result)
|
|
|
|
#Restore scene & remove other scene
|
|
bpy.context.window.scene = previous_scene
|
|
|
|
# Delete compositing scene
|
|
bpy.data.scenes.remove(scene)
|
|
|
|
|
|
|
|
def get_last_item(key_name, collection):
|
|
# bpy.data.images
|
|
# Get last image of a series, e.g. .001, .002, 003
|
|
keys = []
|
|
for item in collection:
|
|
if key_name in item.name:
|
|
keys.append(item.name)
|
|
|
|
print("Search for {}x : '{}'".format(len(keys), ",".join(keys) ) )
|
|
|
|
if len(keys) > 0:
|
|
return collection[keys[-1]]
|
|
|
|
return None
|
|
|
|
|
|
|
|
def setup_image(color_report, mode, name, width, height, tiles, material_load=False):
|
|
preferences = bpy.context.preferences.addons[__package__].preferences
|
|
|
|
if preferences.bool_bake_back_color == 'CUSTOM':
|
|
bake_back_color = bpy.context.scene.texToolsSettings.bake_back_color
|
|
else:
|
|
bake_back_color = modes[mode].color
|
|
|
|
def set_color_space(color_report, image):
|
|
image.alpha_mode = 'NONE'
|
|
try:
|
|
image.colorspace_settings.name = bpy.context.scene.texToolsSettings.bake_color_space
|
|
except:
|
|
try:
|
|
if bpy.context.scene.texToolsSettings.bake_color_space == 'Utility - Linear - sRGB':
|
|
bpy.context.scene.texToolsSettings.bake_color_space = 'Non-Color'
|
|
color_report[0] = "ACES Color Space type is not available"
|
|
elif bpy.context.scene.texToolsSettings.bake_color_space == 'Utility - sRGB - Texture':
|
|
bpy.context.scene.texToolsSettings.bake_color_space = 'sRGB'
|
|
color_report[0] = "ACES Color Space type is not available"
|
|
elif bpy.context.scene.texToolsSettings.bake_color_space == 'Non-Color':
|
|
bpy.context.scene.texToolsSettings.bake_color_space = 'Utility - Linear - sRGB'
|
|
color_report[0] = "Standard RGB Color Space type is not available"
|
|
elif bpy.context.scene.texToolsSettings.bake_color_space == 'sRGB':
|
|
bpy.context.scene.texToolsSettings.bake_color_space = 'Utility - sRGB - Texture'
|
|
color_report[0] = "Standard RGB Color Space type is not available"
|
|
image.colorspace_settings.name = bpy.context.scene.texToolsSettings.bake_color_space
|
|
except:
|
|
color_report[0] = "No one of the known Color Space types is available"
|
|
return None
|
|
|
|
def resize(image):
|
|
if image.size[0] != width or image.size[1] != height or image.generated_width != width or image.generated_height != height:
|
|
image.scale(width, height)
|
|
|
|
def set_image_as_background(image):
|
|
for area in bpy.context.screen.areas:
|
|
if area.ui_type == 'UV':
|
|
area.spaces[0].image = bpy.data.images[image.name]
|
|
|
|
def apply_color(image):
|
|
# Set background color to a small version of the image for performance
|
|
image.pixels = [pv for p in range(4) for pv in bake_back_color]
|
|
# Set final size of the image
|
|
resize(image)
|
|
|
|
def image_create():
|
|
is_float_32 = preferences.bake_32bit_float == '32'
|
|
|
|
if tiles and settings.bversion >= 3.2:
|
|
# Create a full size new tiled image with alpha background
|
|
image = bpy.data.images.new(name, width=width, height=height, alpha=True, float_buffer=is_float_32, tiled=True)
|
|
else:
|
|
# Create a small new image
|
|
image = bpy.data.images.new(name, width=2, height=2, alpha=True, float_buffer=is_float_32, tiled=False)
|
|
|
|
set_image_as_background(image)
|
|
set_color_space(color_report, image)
|
|
|
|
if tiles and settings.bversion >= 3.2:
|
|
image.tiles.get(1001).generated_color = bake_back_color
|
|
for tile in tiles:
|
|
bpy.ops.image.tile_add(number=tile, count=1, label="", fill=True, width=width, height=height, float=is_float_32, alpha=True, color=bake_back_color)
|
|
image.tiles.active_index = 0
|
|
else:
|
|
apply_color(image)
|
|
|
|
#TODO revisit this if image save is implemented
|
|
#image.file_format = 'TARGA'
|
|
return image
|
|
|
|
|
|
if name in bpy.data.images:
|
|
previous_image = bpy.data.images[name]
|
|
if previous_image.source == 'FILE':
|
|
# Clear image if it was deleted or moved outside
|
|
print("Existing image expected path "+bpy.path.abspath(previous_image.filepath))
|
|
if not os.path.isfile(bpy.path.abspath(previous_image.filepath)):
|
|
print("Unlinking missing image "+name)
|
|
image = image_create()
|
|
if material_load:
|
|
return image, None # Not possible Circular Dependency
|
|
return image, previous_image # Avoid Circular Dependency: use method B
|
|
else:
|
|
set_image_as_background(previous_image)
|
|
set_color_space(color_report, previous_image)
|
|
if settings.bversion < 3.2 or not tiles:
|
|
previous_image.scale(2, 2)
|
|
apply_color(previous_image)
|
|
if material_load:
|
|
return previous_image, None # Not possible Circular Dependency
|
|
return previous_image, previous_image # Avoid Circular Dependency: use method A
|
|
else:
|
|
if material_load:
|
|
set_image_as_background(previous_image)
|
|
set_color_space(color_report, previous_image)
|
|
if settings.bversion < 3.2 or not tiles:
|
|
previous_image.scale(2, 2)
|
|
apply_color(previous_image)
|
|
return previous_image, None # Not possible Circular Dependency
|
|
image = image_create()
|
|
return image, previous_image # Avoid Circular Dependency: use method B
|
|
else:
|
|
image = image_create()
|
|
return image, None # Not possible Circular Dependency
|
|
|
|
|
|
|
|
def setup_image_bake_node(obj, bakeReadyMaterials, image_name, previous_image_name, imagecopy_name):
|
|
image = bpy.data.images[image_name]
|
|
if previous_image_name:
|
|
previous_image = bpy.data.images[previous_image_name]
|
|
else:
|
|
previous_image = None
|
|
if imagecopy_name:
|
|
imagecopy = bpy.data.images[imagecopy_name]
|
|
else:
|
|
imagecopy = None
|
|
|
|
if len(obj.data.materials) <= 0:
|
|
print("ERROR, need spare material to setup active image texture to bake!!!")
|
|
|
|
else:
|
|
def avoid_circular(tree, image):
|
|
# Avoid Circular Dependency method A
|
|
for node in tree.nodes:
|
|
if node.bl_idname == 'ShaderNodeTexImage':
|
|
if node.image == image:
|
|
node.image = imagecopy
|
|
|
|
def assign_node(tree):
|
|
# Assign bake node
|
|
node = None
|
|
if "TexTools_bake" in tree.nodes:
|
|
node = tree.nodes["TexTools_bake"]
|
|
else:
|
|
node = tree.nodes.new("ShaderNodeTexImage")
|
|
node.name = "TexTools_bake"
|
|
node.select = True
|
|
node.image = image
|
|
tree.nodes.active = node
|
|
|
|
for slot in obj.material_slots:
|
|
if slot.material:
|
|
if slot.material.name not in bakeReadyMaterials:
|
|
if image == previous_image:
|
|
if slot.material.use_nodes == False:
|
|
# No need to Avoid Circular Dependency
|
|
slot.material.use_nodes = True
|
|
assign_node(slot.material.node_tree)
|
|
else:
|
|
# Use Avoid Circular Dependency method A to preserve external linking of the existing previous_image
|
|
avoid_circular(slot.material.node_tree, image)
|
|
assign_node(slot.material.node_tree)
|
|
else:
|
|
# Avoid Circular Dependency method B is used just by not baking directly on the existing previous_image
|
|
if slot.material.use_nodes == False:
|
|
slot.material.use_nodes = True
|
|
assign_node(slot.material.node_tree)
|
|
|
|
bakeReadyMaterials.append(slot.material.name)
|
|
|
|
|
|
|
|
def relink_nodes(mode, material):
|
|
if material.use_nodes == False:
|
|
material.use_nodes = True
|
|
tree = material.node_tree
|
|
bsdf_node = None
|
|
for n in tree.nodes:
|
|
if n.bl_idname == "ShaderNodeBsdfPrincipled":
|
|
bsdf_node = n
|
|
|
|
# set b, which is the base(original) socket index, and n, which is the new-values-source index for the base socket
|
|
b, n = modes[mode].relink['b'], modes[mode].relink['n']
|
|
|
|
base_node = base_socket = None
|
|
if len(bsdf_node.inputs[b].links) != 0 :
|
|
base_node = bsdf_node.inputs[b].links[0].from_node
|
|
base_socket = bsdf_node.inputs[b].links[0].from_socket.name
|
|
base_value = (bsdf_node.inputs[b].default_value, )
|
|
# If the base value is a color, decompose its value so it can be stored and recovered later, otherwise its value will change while the swap of sockets is committed
|
|
if not isinstance(base_value[0], float):
|
|
base_value = ((base_value[0][0],base_value[0][1],base_value[0][2],base_value[0][3]), )
|
|
new_node = None
|
|
|
|
if len(bsdf_node.inputs[n].links) != 0 :
|
|
new_node = bsdf_node.inputs[n].links[0].from_node
|
|
new_node_socket = bsdf_node.inputs[n].links[0].from_socket.name
|
|
if (new_node == base_node and new_node != None) and base_socket == new_node_socket :
|
|
pass
|
|
else:
|
|
bsdf_node.inputs[b].default_value = bsdf_node.inputs[n].default_value
|
|
tree.links.new(new_node.outputs[new_node_socket], bsdf_node.inputs[b])
|
|
else:
|
|
if base_node:
|
|
tree.links.remove(bsdf_node.inputs[b].links[0])
|
|
bsdf_node.inputs[b].default_value = bsdf_node.inputs[n].default_value
|
|
|
|
|
|
|
|
def channel_ignore(channel, material):
|
|
if material.use_nodes == False:
|
|
material.use_nodes = True
|
|
tree = material.node_tree
|
|
bsdf_node = None
|
|
for n in tree.nodes:
|
|
if n.bl_idname == "ShaderNodeBsdfPrincipled":
|
|
bsdf_node = n
|
|
|
|
if len(bsdf_node.inputs[channel].links) != 0 :
|
|
tree.links.remove(bsdf_node.inputs[channel].links[0])
|
|
|
|
# So far, Channels whose effect on others is wanted to be ignored have to be set equal to 1.0
|
|
bsdf_node.inputs[channel].default_value = 1.0
|
|
|
|
|
|
|
|
def setup_material_loaded(mode, name):
|
|
material_bake = bpy.data.materials[name]
|
|
if mode == 'wireframe':
|
|
if "Value" in material_bake.node_tree.nodes:
|
|
material_bake.node_tree.nodes["Value"].outputs[0].default_value = bpy.context.scene.texToolsSettings.bake_wireframe_size
|
|
if mode == 'bevel_mask':
|
|
if "Bevel" in material_bake.node_tree.nodes:
|
|
material_bake.node_tree.nodes["Bevel"].inputs[0].default_value = bpy.context.scene.texToolsSettings.bake_bevel_size
|
|
material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples
|
|
if mode == 'normal_tangent_bevel':
|
|
if "Bevel" in material_bake.node_tree.nodes:
|
|
material_bake.node_tree.nodes["Bevel"].inputs[0].default_value = bpy.context.scene.texToolsSettings.bake_bevel_size
|
|
material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples
|
|
if mode == 'normal_object_bevel':
|
|
if "Bevel" in material_bake.node_tree.nodes:
|
|
material_bake.node_tree.nodes["Bevel"].inputs[0].default_value = bpy.context.scene.texToolsSettings.bake_bevel_size
|
|
material_bake.node_tree.nodes["Bevel"].samples = bpy.context.scene.texToolsSettings.bake_bevel_samples
|
|
if mode == 'thickness':
|
|
if "ao" in material_bake.node_tree.nodes:
|
|
material_bake.node_tree.nodes["ao"].samples = bpy.context.scene.texToolsSettings.bake_samples
|
|
material_bake.node_tree.nodes["ao"].only_local = bpy.context.scene.texToolsSettings.bake_thickness_local
|
|
if "Distance" in material_bake.node_tree.nodes:
|
|
material_bake.node_tree.nodes["Distance"].outputs[0].default_value = bpy.context.scene.texToolsSettings.bake_thickness_distance
|
|
if "Contrast" in material_bake.node_tree.nodes:
|
|
material_bake.node_tree.nodes["Contrast"].outputs[0].default_value = bpy.context.scene.texToolsSettings.bake_thickness_contrast
|
|
|
|
|
|
|
|
def get_material(mode):
|
|
if modes[mode].material == "":
|
|
return None # No material setup required
|
|
|
|
# Find or load material
|
|
name = modes[mode].material
|
|
path = os.path.join(os.path.dirname(__file__), "resources/materials.blend", "Material")
|
|
if "bevel" in mode or "thickness" in mode:
|
|
path = os.path.join(os.path.dirname(__file__), "resources/materials_2.80.blend", "Material")
|
|
#print("Get material {}\n{}".format(mode, path))
|
|
|
|
if bpy.data.materials.get(name) is None:
|
|
#print("Material not yet loaded: "+mode)
|
|
bpy.ops.wm.append(filename=name, directory=path, link=False, autoselect=False)
|
|
|
|
return name
|
|
|
|
|
|
|
|
def cycles_bake(mode, padding, sampling_scale, samples, cage_extrusion, ray_distance, is_multi, obj_cage):
|
|
|
|
# if modes[mode].engine == 'BLENDER_EEVEE':
|
|
# # Snippet: https://gist.github.com/AndrewRayCode/760c4634a77551827de41ed67585064b
|
|
# bpy.context.scene.render.bake_margin = padding
|
|
|
|
# # AO Settings
|
|
# bpy.context.scene.render.bake_type = modes[mode].type
|
|
# bpy.context.scene.render.use_bake_normalize = True
|
|
|
|
# if modes[mode].type == 'AO':
|
|
# bpy.context.scene.world.light_settings.use_ambient_occlusion = True
|
|
# bpy.context.scene.world.light_settings.gather_method = 'RAYTRACE'
|
|
# bpy.context.scene.world.light_settings.samples = samples
|
|
|
|
# bpy.context.scene.render.use_bake_selected_to_active = is_multi
|
|
# bpy.context.scene.render.bake_distance = ray_distance
|
|
# bpy.context.scene.render.use_bake_clear = False
|
|
|
|
# bpy.ops.object.bake_image()
|
|
|
|
if modes[mode].engine == 'CYCLES' or modes[mode].engine == 'BLENDER_EEVEE' :
|
|
|
|
if modes[mode].normal_space == 'OBJECT':
|
|
#See: https://twitter.com/Linko_3D/status/963066705584054272
|
|
bpy.context.scene.render.bake.normal_r = 'POS_X'
|
|
bpy.context.scene.render.bake.normal_g = 'POS_Z'
|
|
bpy.context.scene.render.bake.normal_b = 'NEG_Y'
|
|
|
|
elif modes[mode].normal_space == 'TANGENT':
|
|
bpy.context.scene.render.bake.normal_r = 'POS_X'
|
|
bpy.context.scene.render.bake.normal_b = 'POS_Z'
|
|
# Adjust Y swizzle from Addon preferences
|
|
swizzle_y = bpy.context.preferences.addons[__package__].preferences.swizzle_y_coordinate
|
|
if swizzle_y == 'Y-':
|
|
bpy.context.scene.render.bake.normal_g = 'NEG_Y'
|
|
elif swizzle_y == 'Y+':
|
|
bpy.context.scene.render.bake.normal_g = 'POS_Y'
|
|
|
|
# Set samples
|
|
bpy.context.scene.cycles.samples = samples
|
|
|
|
# Speed up samples for simple render modes
|
|
if modes[mode].type in {'EMIT', 'DIFFUSE', 'ROUGHNESS', 'TRANSMISSION', 'ENVIRONMENT', 'UV'} and mode != 'thickness':
|
|
bpy.context.scene.cycles.samples = 1
|
|
|
|
# Pixel Padding
|
|
bpy.context.scene.render.bake.margin = padding * sampling_scale
|
|
|
|
# Disable Direct and Indirect for all 'DIFFUSE' bake types
|
|
if modes[mode].type == 'DIFFUSE':
|
|
bpy.context.scene.render.bake.use_pass_direct = False
|
|
bpy.context.scene.render.bake.use_pass_indirect = False
|
|
bpy.context.scene.render.bake.use_pass_color = True
|
|
|
|
if settings.bversion < 2.90:
|
|
if obj_cage is None:
|
|
bpy.ops.object.bake(
|
|
type=modes[mode].type,
|
|
use_clear=False,
|
|
use_selected_to_active=is_multi,
|
|
cage_extrusion=cage_extrusion,
|
|
normal_space=modes[mode].normal_space
|
|
)
|
|
else:
|
|
bpy.ops.object.bake(
|
|
type=modes[mode].type,
|
|
use_clear=False,
|
|
use_selected_to_active=is_multi,
|
|
cage_extrusion=cage_extrusion,
|
|
normal_space=modes[mode].normal_space,
|
|
use_cage=True,
|
|
cage_object=obj_cage.name
|
|
)
|
|
else:
|
|
if obj_cage is None:
|
|
bpy.ops.object.bake(
|
|
type=modes[mode].type,
|
|
use_clear=False,
|
|
use_selected_to_active=is_multi,
|
|
cage_extrusion=cage_extrusion,
|
|
max_ray_distance=ray_distance,
|
|
normal_space=modes[mode].normal_space
|
|
)
|
|
else:
|
|
bpy.ops.object.bake(
|
|
type=modes[mode].type,
|
|
use_clear=False,
|
|
use_selected_to_active=is_multi,
|
|
cage_extrusion=cage_extrusion,
|
|
max_ray_distance=ray_distance,
|
|
normal_space=modes[mode].normal_space,
|
|
use_cage=True,
|
|
cage_object=obj_cage.name
|
|
)
|
|
|
|
|
|
bpy.utils.register_class(op)
|