Files
blender-portable-repo/scripts/addons/SimpleBake/image_management.py
T
2026-07-16 12:41:53 -06:00

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)