400 lines
15 KiB
Python
400 lines
15 KiB
Python
import bpy
|
|
from bpy.types import Operator
|
|
from bpy.utils import register_class, unregister_class
|
|
from bpy.props import BoolProperty, StringProperty, IntProperty
|
|
|
|
import uuid
|
|
import numpy as np
|
|
import os
|
|
|
|
from .utils import SBConstants, get_udim_tiles, get_cached_udim_tiles, get_bake_objects, blender_default_colorspace
|
|
from .messages import print_message
|
|
from .background_and_progress import BakeInProgress as Bip
|
|
from .aov import get_aov_colspace, get_aov_type, get_aov_name
|
|
from . import __package__ as base_package
|
|
|
|
def gen_image_name(context, name, globalmode, baketype, mat_name=""):
|
|
sbp = context.scene.SimpleBake_Props
|
|
prefs = context.preferences.addons[base_package].preferences
|
|
proposed_name = prefs.img_name_format
|
|
|
|
if SBConstants.PBRAOVS in baketype:
|
|
baketype = get_aov_name(context, baketype)
|
|
|
|
filepath = bpy.data.filepath
|
|
if filepath:
|
|
filename_no_ext = os.path.splitext(os.path.basename(filepath))[0]
|
|
else:
|
|
filename_no_ext = ""
|
|
|
|
|
|
if Bip.Sequence.active:
|
|
if "%FRAME%" not in proposed_name:
|
|
proposed_name = f"{proposed_name}.%FRAME%"
|
|
proposed_name = proposed_name.replace("%FRAME%", f"{context.scene.frame_current:04d}")
|
|
else:
|
|
proposed_name = proposed_name.replace(".%FRAME%", "")
|
|
proposed_name = proposed_name.replace("%FRAME%", "")
|
|
|
|
proposed_name = proposed_name.replace("%OBJ%", name)
|
|
proposed_name = proposed_name.replace("%BAKEMODE%", globalmode)
|
|
proposed_name = proposed_name.replace("%BAKETYPE%", baketype)
|
|
proposed_name = proposed_name.replace("%BATCH%", sbp.batch_name)
|
|
proposed_name = proposed_name.replace("%BLEND_FILE_NAME%", filename_no_ext)
|
|
res = f"{sbp.outputwidth}x{sbp.outputheight}"
|
|
if prefs.abbr_res and sbp.outputwidth % 1024 == 0 and sbp.outputheight % 1024 == 0:
|
|
res = f"{int(sbp.outputwidth / 1024)}K"
|
|
proposed_name = proposed_name.replace("%RESOLUTION%", res)
|
|
|
|
if mat_name:
|
|
if "%MAT%" in proposed_name:
|
|
proposed_name = proposed_name.replace("%MAT%", mat_name)
|
|
else:
|
|
proposed_name = f"{proposed_name}_{mat_name}"
|
|
else:
|
|
proposed_name = proposed_name.replace("%MAT%", "")
|
|
|
|
return proposed_name
|
|
|
|
|
|
class SimpleBake_OT_Bake_Image(Operator):
|
|
"""Generate image to be baked to if needed"""
|
|
bl_idname = "simplebake.bake_image"
|
|
bl_description = "Generates image to be baked to if needed"
|
|
bl_label = "GenImage"
|
|
|
|
need_first_time_settings = []
|
|
|
|
|
|
bake_operation_id: StringProperty()
|
|
this_bake: StringProperty()
|
|
target_object_name: StringProperty()
|
|
global_mode: StringProperty()
|
|
bake_mode: StringProperty()
|
|
|
|
|
|
|
|
def create_individual_bake_image(self, context):
|
|
sbp = context.scene.SimpleBake_Props
|
|
print_message(context, "Getting individual baked image")
|
|
|
|
prefs = context.preferences.addons[base_package].preferences
|
|
|
|
alias_dict = prefs.get_alias_dict()
|
|
if self.this_bake in alias_dict: this_bake = alias_dict[self.this_bake]
|
|
else: this_bake = self.this_bake
|
|
|
|
proposed_name = gen_image_name(context, self.target_object_name, self.global_mode, this_bake)
|
|
|
|
need_new = False
|
|
if proposed_name in bpy.data.images and not self.clear_image:
|
|
#It's there, and we aren't going to clear it
|
|
|
|
#No action for now
|
|
i = bpy.data.images[proposed_name]
|
|
self.created_images.append(i)
|
|
self.set_image_tags(i)
|
|
need_new = False
|
|
|
|
elif proposed_name in bpy.data.images:
|
|
#It's there, but we want to clear it
|
|
if sbp.replace_previous_bakes:
|
|
bpy.data.images.remove(bpy.data.images[proposed_name])
|
|
need_new = True
|
|
|
|
else:
|
|
#It's not there
|
|
need_new = True
|
|
|
|
if need_new:
|
|
#Actually Create the image
|
|
tiled = True if (self.total_tiles > 1 and sbp.auto_detect_udims) else False
|
|
ua = sbp.texture_bg_col[3] < 1.0
|
|
i = bpy.data.images.new(proposed_name, self.img_width, self.img_height, alpha=ua, float_buffer=self.float_buffer, tiled=tiled)
|
|
|
|
|
|
self.created_images.append(i)
|
|
self.set_image_tags(i)
|
|
self.need_first_time_settings.append(i)
|
|
else:
|
|
#self.need_first_time_settings = False
|
|
pass
|
|
|
|
|
|
def create_merged_bake_image(self, context):
|
|
sbp = context.scene.SimpleBake_Props
|
|
print_message(context, "Getting merged bake image")
|
|
|
|
prefs = context.preferences.addons[base_package].preferences
|
|
|
|
alias_dict = prefs.get_alias_dict()
|
|
if self.this_bake in alias_dict: this_bake = alias_dict[self.this_bake]
|
|
else: this_bake = self.this_bake
|
|
|
|
proposed_name = gen_image_name(context, self.merged_bake_name, self.global_mode, this_bake)
|
|
|
|
need_new = False
|
|
|
|
if proposed_name not in bpy.data.images:
|
|
need_new = True
|
|
else:
|
|
i = bpy.data.images[proposed_name]
|
|
if "SB_bake_operation_id" in i and i["SB_bake_operation_id"] != self.bake_operation_id:
|
|
#It's there, but different bake ID
|
|
if self.clear_image:
|
|
if sbp.replace_previous_bakes:
|
|
bpy.data.images.remove(i)
|
|
need_new = True
|
|
else:
|
|
#Essentially do nothing
|
|
pass
|
|
|
|
elif "SB_bake_operation_id" not in i:
|
|
#It's there, but it doesn't even have an ID. This will be a weird coincidence
|
|
if self.clear_image:
|
|
if sbp.replace_previous_bakes:
|
|
bpy.data.images.remove(i)
|
|
need_new = True
|
|
else:
|
|
#Essentially do nothing
|
|
pass
|
|
|
|
elif "SB_bake_operation_id" in i and i["SB_bake_operation_id"] == self.bake_operation_id:
|
|
#It's there, and it's our bake
|
|
need_new = False
|
|
|
|
if need_new:
|
|
tiled = True if (self.total_tiles > 1 and sbp.auto_detect_udims) else False
|
|
ua = sbp.texture_bg_col[3] < 1.0
|
|
i = bpy.data.images.new(proposed_name, self.img_width, self.img_height, alpha=ua, float_buffer=self.float_buffer, tiled=tiled)
|
|
|
|
#Create all the tiles
|
|
# if tiled:
|
|
# for t in self.active_tiles:
|
|
# i.tiles.new(t)
|
|
|
|
self.created_images.append(i)
|
|
self.set_image_tags(i)
|
|
self.need_first_time_settings.append(i)
|
|
|
|
else:
|
|
#Just using existing image, pretend we created it
|
|
#i is created up on line 113
|
|
self.created_images.append(i)
|
|
self.set_image_tags(i)
|
|
|
|
|
|
|
|
def create_per_mat_bake_image(self, context):
|
|
"""Create one bake image per enabled material for tex_per_mat mode."""
|
|
sbp = context.scene.SimpleBake_Props
|
|
prefs = context.preferences.addons[base_package].preferences
|
|
|
|
alias_dict = prefs.get_alias_dict()
|
|
this_bake = alias_dict.get(self.this_bake, self.this_bake)
|
|
|
|
# Get enabled materials for this object from mat_bake_list
|
|
all_entries = [e for e in sbp.mat_bake_list if e.obj_name == self.target_object_name]
|
|
enabled_mats = [e.mat_name for e in all_entries if e.enabled]
|
|
|
|
# If the object has no mat_bake_list entries at all (e.g. it's an S2A target object),
|
|
# fall back to standard single-image creation.
|
|
# If it has entries but all are disabled, skip image creation entirely.
|
|
if not all_entries:
|
|
self.create_individual_bake_image(context)
|
|
return
|
|
if not enabled_mats:
|
|
return
|
|
|
|
for mat_name in enabled_mats:
|
|
proposed_name = gen_image_name(
|
|
context, self.target_object_name, self.global_mode, this_bake,
|
|
mat_name=mat_name
|
|
)
|
|
|
|
need_new = False
|
|
if proposed_name in bpy.data.images and not self.clear_image:
|
|
i = bpy.data.images[proposed_name]
|
|
self.created_images.append(i)
|
|
self.set_image_tags(i, mat_name=mat_name)
|
|
need_new = False
|
|
elif proposed_name in bpy.data.images:
|
|
if sbp.replace_previous_bakes:
|
|
bpy.data.images.remove(bpy.data.images[proposed_name])
|
|
need_new = True
|
|
else:
|
|
need_new = True
|
|
|
|
if need_new:
|
|
tiled = True if (self.total_tiles > 1 and sbp.auto_detect_udims) else False
|
|
ua = sbp.texture_bg_col[3] < 1.0
|
|
i = bpy.data.images.new(
|
|
proposed_name, self.img_width, self.img_height,
|
|
alpha=ua, float_buffer=self.float_buffer, tiled=tiled
|
|
)
|
|
self.created_images.append(i)
|
|
self.set_image_tags(i, mat_name=mat_name)
|
|
self.need_first_time_settings.append(i)
|
|
|
|
def set_image_tags(self, i, mat_name=""):
|
|
i["SB_bake_object"] = self.target_object_name
|
|
i["SB_global_mode"] = self.global_mode
|
|
i["SB_this_bake"] = self.this_bake
|
|
i["SB_merged_bake"] = self.merged_bake
|
|
i["SB_merged_bake_name"] = self.merged_bake_name
|
|
i["SB_bake_operation_id"] = self.bake_operation_id
|
|
i["SB_float_buffer"] = self.float_buffer
|
|
i["SB_scaled"] = False
|
|
i["SB_exported_list"] = []
|
|
i["SB_denoised"] = False
|
|
i["SB_aa"] = False
|
|
if mat_name:
|
|
i["SB_bake_material"] = mat_name
|
|
|
|
|
|
def set_image_settings(self, context, imgs):
|
|
|
|
sbp = context.scene.SimpleBake_Props
|
|
|
|
for i in imgs:
|
|
print_message(context, f"Adjusting settings on new image {i.name}")
|
|
|
|
if self.this_bake == SBConstants.PBR_NORMAL:
|
|
gc = (0.5,0.5,1.0,1.0) #Normal ignores the use alpha option
|
|
elif self.this_bake == "NORMAL": #CyclesBake normal
|
|
gc = (0.5,0.5,1.0,1.0) #Normal ignores the use alpha option
|
|
#elif self.use_alpha:
|
|
#gc = (0.0,0.0,0.0,0.0)
|
|
else:
|
|
gc = sbp.texture_bg_col
|
|
|
|
i.generated_color = gc
|
|
#If this is a tiled image, let's go for all the tiles too
|
|
#if len(i.tiles) > 1: #Even an untiled image has tile length of 1
|
|
|
|
if i.source == 'TILED':
|
|
area = context.screen.areas[0]
|
|
|
|
old_type = area.type
|
|
area.type = "IMAGE_EDITOR"
|
|
area.spaces[0].image = i
|
|
with context.temp_override(area=area):
|
|
|
|
for t in self.active_tiles:
|
|
if t==1001:
|
|
continue
|
|
print(f"Going to add tile {t}")
|
|
ua = sbp.texture_bg_col[3] < 1.0
|
|
bpy.ops.image.tile_add(number=t, count=1, label="", fill=True, color=gc, generated_type='BLANK', width=self.img_width, height=self.img_height, float=self.float_buffer, alpha=ua)
|
|
|
|
|
|
area.type = old_type
|
|
|
|
print(f"Len tiles now {len(i.tiles)}")
|
|
|
|
|
|
prefs = context.preferences.addons[base_package].preferences
|
|
cs_dict = prefs.get_cs_dict()
|
|
|
|
|
|
if self.this_bake in cs_dict:
|
|
cs = cs_dict[self.this_bake]
|
|
elif self.global_mode in [SBConstants.CYCLESBAKE,SBConstants.CYCLESBAKE_S2A] and context.scene.cycles.bake_type != "NORMAL":
|
|
cs = sbp.cyclesbake_cs
|
|
elif SBConstants.PBRAOVS in self.this_bake:
|
|
print(f"AOV DETECTED BASED ON {self.this_bake}")
|
|
cs = get_aov_colspace(context, self.this_bake)
|
|
else:
|
|
cs = blender_default_colorspace(float_buffer=False, col=True)[1]
|
|
|
|
i.colorspace_settings.name = cs
|
|
print_message(context, f"Image colour space set to {cs}")
|
|
|
|
i.use_fake_user = True
|
|
|
|
|
|
|
|
def execute(self, context):
|
|
|
|
|
|
sbp = context.scene.SimpleBake_Props
|
|
self.merged_bake = sbp.merged_bake
|
|
self.total_tiles = 0
|
|
self.active_tiles = []
|
|
|
|
if sbp.auto_detect_udims:
|
|
if not self.merged_bake:
|
|
#Nice and simple
|
|
r = get_cached_udim_tiles(context, self.target_object_name)
|
|
self.total_tiles = r["total_tiles"]
|
|
self.active_tiles = r["active_tiles"]
|
|
print_message(context, f"Object {self.target_object_name} has maximum UDIM tile {self.total_tiles}")
|
|
print_message(context, f"Object {self.target_object_name} has active UDIM tiles {self.active_tiles}")
|
|
else:
|
|
#Way more complicated
|
|
self.active_tiles = set()
|
|
self.total_tiles = 0
|
|
objects = get_bake_objects(context)
|
|
for o_name in objects:
|
|
if not (o:=context.scene.objects.get(o_name)):
|
|
continue
|
|
r = get_cached_udim_tiles(context, o.name)
|
|
these_active_tiles = r["active_tiles"]
|
|
self.active_tiles.update(these_active_tiles)
|
|
|
|
this_total_tiles = r["total_tiles"]
|
|
self.total_tiles = this_total_tiles if this_total_tiles > self.total_tiles else self.total_tiles
|
|
self.active_tiles = list(self.active_tiles)
|
|
|
|
print_message(context, f"Merged bake with maximum UDIM tile {self.total_tiles}")
|
|
print_message(context, f"Merged bake with active UDIM tiles {self.active_tiles}")
|
|
|
|
|
|
|
|
#Grab what we need from the panel
|
|
self.merged_bake_name = sbp.merged_bake_name
|
|
#self.use_alpha = sbp.use_alpha
|
|
self.img_height = sbp.imgheight
|
|
self.img_width = sbp.imgwidth
|
|
self.clear_image = sbp.clear_image
|
|
self.float_buffer = True if (self.bake_mode == SBConstants.PBR_NORMAL and not sbp.no_force_32bit_normals) or sbp.everything32bitfloat else False
|
|
|
|
self.need_first_time_settings = []
|
|
self.created_images = []
|
|
|
|
|
|
if self.merged_bake:
|
|
self.create_merged_bake_image(context)
|
|
elif sbp.tex_per_mat:
|
|
self.create_per_mat_bake_image(context)
|
|
else:
|
|
self.create_individual_bake_image(context)
|
|
|
|
|
|
#We had to create a new image
|
|
if self.created_images != []:
|
|
#Set image settings for images that are freshly created
|
|
self.set_image_settings(context, self.need_first_time_settings)
|
|
|
|
else:
|
|
print_message(context, "Using existing image")
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
|
|
classes = ([
|
|
SimpleBake_OT_Bake_Image
|
|
])
|
|
|
|
def register():
|
|
|
|
global classes
|
|
for cls in classes:
|
|
register_class(cls)
|
|
|
|
def unregister():
|
|
global classes
|
|
for cls in classes:
|
|
unregister_class(cls)
|