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

749 lines
26 KiB
Python

import bpy
from bpy.types import Operator, Macro
from bpy.props import StringProperty, BoolProperty
from bpy.utils import register_class, unregister_class
import sys
from pathlib import Path
import base64
import mathutils
import tempfile
import os
import numpy as np
suppress_for_preset_load = False
_disable_impossible_running = False
def blender_refresh():
return True
# print_message(context, "Blender refresh")
# bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
# for area in context.screen.areas:
# area.tag_redraw()
# context.view_layer.update()
# context.view_layer.depsgraph.update()
#Takes a path object
def can_write_to_location(dir_path):
#First check if the folder already exists or not
if not os.path.exists(str(dir_path)):
#Find the first parent that does exist, see if we can write there
last = ""
while not os.path.exists(str(dir_path)):
dir_path = dir_path.parent
if str(dir_path) == last:
break
last = str(dir_path)
test_file_path = dir_path / "test_file.tmp"
try:
# Attempt to create a temporary file in the directory
with open(str(test_file_path), 'w') as temp_file:
temp_file.write("Test")
# If successful, remove the test file
os.remove(str(test_file_path))
return True
except PermissionError:
# Permission denied
return False
except Exception as e:
# Other exceptions could be handled differently if needed
print(f"An error occurred: {e}")
return False
def disable_impossible(context):
global suppress_for_preset_load, _disable_impossible_running
if _disable_impossible_running:
return
_disable_impossible_running = True
try:
sbp = context.scene.SimpleBake_Props
suppress_for_preset_load = True
#Start with a refresh of the objects list
bpy.ops.simplebake.refresh_bake_object_list()
#And AOVs
bpy.ops.simplebake.refresh_aov_list()
#And UVs
bpy.ops.simplebake.sync_uv_list()
if bpy.app.version >= (4,0,0):
sbp.selected_transrough = False
sbp.selected_ssscol = False
if not sbp.save_bakes_external:
sbp.rundenoise = False
sbp.rough_glossy_switch = SBConstants.PBR_ROUGHNESS
sbp.ccrough_glossy_switch = SBConstants.PBR_CLEARCOAT_ROUGH
sbp.normal_format_switch = SBConstants.NORMAL_OPENGL
sbp.multiply_diffuse_ao = "purediffuse"
sbp.do_aa = False
sbp.save_obj_external = False
for cpt in sbp.cp_list:
if "png_compression" not in cpt:
cpt.png_compression = 15
#if sbp.fbx_preset_name == "":
#sbp.fbx_preset_name = "None"
if sbp.multiply_diffuse_ao == "diffusexao" and sbp.global_mode == SBConstants.PBR:
sbp.selected_ao = True
if sbp.s2a_opmode == "decals":
sbp.multiply_diffuse_ao = "purediffuse"
if not sbp.keep_internal_after_export:
sbp.copy_and_apply = False
if sbp.tex_per_mat and (sbp.selected_s2a or sbp.cycles_s2a):
sbp.tex_per_mat = False
if sbp.tex_per_mat and sbp.expand_mat_uvs:
sbp.new_uv_option = True
if sbp.tex_per_mat and not sbp.expand_mat_uvs:
sbp.new_uv_option = False
if not sbp.merged_bake or not (sbp.export_mesh_individual_or_combined == "combined"):
sbp.merge_export_obj = False
if not sbp.merged_bake:
sbp.join_objs_to_proxy = False
finally:
suppress_for_preset_load = False
_disable_impossible_running = False
def get_bake_objects(context):
sbp = context.scene.SimpleBake_Props
#Work out our object selection
objects = []
if (sbp.selected_s2a or sbp.cycles_s2a) and sbp.s2a_opmode=="automatch":
from .bake_operators.auto_match_operators import SimpleBake_OT_AutoMatch_Operation as amo
#objects = [context.scene.objects[i] for i in list(amo.hl_matches.keys())]
objects = list(amo.hl_matches.keys())
elif sbp.selected_s2a: #Standard S2A bake or Decals
objects = [sbp.targetobj.name] if sbp.targetobj != None else []
elif sbp.cycles_s2a: #Standard S2A bake
objects = [sbp.targetobj_cycles.name] if sbp.targetobj_cycles != None else []
else:
objects = [i.obj_point.name for i in sbp.objects_list]
return objects
#active_tiles is sorted list of UDIM numbers (1001, 1002 etc.)
#total_tiles is highest tile index (e.g. 11 if the highest numbered active tile is 1011)
def get_udim_tiles(context, object_name, threshold=0.001):
import numpy as np
obj = context.scene.objects.get(object_name)
if not obj or not hasattr(obj.data, 'uv_layers') or not obj.data.uv_layers:
return {"active_tiles": [], "total_tiles": 0, "bbox_tiles": 0}
uv_layer = obj.data.uv_layers.active
if uv_layer is None:
return {"active_tiles": [], "total_tiles": 0, "bbox_tiles": 0}
n = len(uv_layer.data)
if n == 0:
return {"active_tiles": [], "total_tiles": 0, "bbox_tiles": 0}
buf = np.empty(n * 2, dtype=np.float64)
uv_layer.data.foreach_get("uv", buf)
uvs = buf.reshape(-1, 2)
u = uvs[:, 0].copy()
v = uvs[:, 1].copy()
# Snap UV coordinates that sit very close to the upper edge of a tile back
# into that tile, to avoid a vertex at e.g. v=0.9999 being counted as tile 1.
frac_u = u % 1.0
frac_v = v % 1.0
u[frac_u > 1.0 - threshold] -= threshold
v[frac_v > 1.0 - threshold] -= threshold
# Subtract a tiny epsilon before flooring so that vertices sitting exactly
# on an integer boundary (e.g. v=1.0, where 1.0 % 1.0 == 0.0) snap to the
# tile below rather than the tile above.
tu = np.floor(u - 1e-9).astype(np.int32)
tv = np.floor(v - 1e-9).astype(np.int32)
mask = (tu >= 0) & (tu <= 9) & (tv >= 0) & (tv <= 9)
tile_nums = 1001 + tu[mask] + 10 * tv[mask]
import math
u_extent = math.ceil(float(np.max(u))) - math.floor(float(np.min(u)))
v_extent = math.ceil(float(np.max(v))) - math.floor(float(np.min(v)))
bbox_tiles = max(1, u_extent) * max(1, v_extent)
if tile_nums.size == 0:
return {"active_tiles": [], "total_tiles": 0, "bbox_tiles": bbox_tiles}
udim_tiles = set(np.unique(tile_nums).tolist())
return {
"active_tiles": sorted(udim_tiles),
"total_tiles": max(udim_tiles) - 1000,
"bbox_tiles": bbox_tiles
}
# def get_max_udim_tiles_from_bake_objects(context):
# sbp = context.scene.SimpleBake_Props
# objs = [i.name for i in sbp.objects_list]
#
# max = 0
#
# for obj_name in objs:
# try:
# d = get_udim_tiles(context, obj_name)
# if len(d["active_tiles"]) > max:
# max = len(d["active_tiles"])
# except:
# pass
#
# return max
# --- UDIM tile cache -----------------------------------------------------------
# Keyed by object name. Dirty set contains names needing recompute.
_udim_tile_cache: dict = {}
_udim_dirty_objects: set = set()
def invalidate_udim_cache(object_names=None):
"""Mark objects as needing recompute. Pass None to invalidate everything."""
global _udim_dirty_objects, _udim_tile_cache
if object_names is None:
_udim_tile_cache.clear()
_udim_dirty_objects.clear()
else:
for name in object_names:
_udim_dirty_objects.add(name)
@bpy.app.handlers.persistent
def _sb_udim_depsgraph_handler(scene, depsgraph):
for update in depsgraph.updates:
if not update.is_updated_geometry:
continue
id_data = update.id
if isinstance(id_data, bpy.types.Object) and id_data.type == 'MESH':
# Topology change on the object itself
_udim_dirty_objects.add(id_data.name)
elif isinstance(id_data, bpy.types.Mesh):
# UV edits come through as mesh data-block updates —
# find all objects that use this mesh and dirty them
for obj in scene.objects:
if obj.type == 'MESH' and obj.data == id_data:
_udim_dirty_objects.add(obj.name)
def get_cached_udim_tiles(context, object_name, threshold=0.001):
global _udim_tile_cache, _udim_dirty_objects
if object_name in _udim_dirty_objects or object_name not in _udim_tile_cache:
#Cache result from the "real" method
_udim_tile_cache[object_name] = get_udim_tiles(context, object_name, threshold)
#Now clean
_udim_dirty_objects.discard(object_name)
return _udim_tile_cache[object_name]
# ------------------------------------------------------------------------------
def find_closest_obj(context, object_name, max_distance=0.5):
ref_obj = context.scene.objects.get(object_name)
if not ref_obj:
raise ValueError(f"No object found with the name '{object_name}'")
closest_mesh = None
closest_distance = float('inf')
for obj in context.scene.objects:
if obj.type == 'MESH' and obj != ref_obj and "SB_replaced_orig_name" not in obj:
distance = (ref_obj.location - obj.location).length
if distance < closest_distance:
closest_distance = distance
closest_mesh = obj
return closest_mesh if closest_distance <= max_distance else None
class SBConstants:
#Constants
PBR = "PBR"
PBRS2A = "PBR StoA"
PBRS2A_AUTOMATCH = "PBR StoA AutoMatch"
PBRS2A_DECALS = "PBR StoA Decals"
CYCLESBAKE = "CyclesBake"
CYCLESBAKE_S2A = "CyclesBake S2A"
CYCLESBAKES2A_AUTOMATCH = "CyclesBake S2A Auto"
SPECIALS = "Specials"
SPECIALS_PBR_TARGET_ONLY = "specials_pbr_targetonly"
SPECIALS_CYCLES_TARGET_ONLY = "specials_cycles_targetonly"
PBRAOVS = "AOV"
#PBR Names
PBR_DIFFUSE = "Diffuse"
PBR_METAL = "Metalness"
PBR_SSS = "SSS"
PBR_SSS_SCALE = "SSS Scale"
PBR_SSSCOL = "SSS Colour"
PBR_ROUGHNESS = "Roughness"
PBR_GLOSSY = "Glossiness"
PBR_NORMAL = "Normal"
PBR_TRANSMISSION = "Transmission"
PBR_TRANSMISSION_ROUGH = "Transmission Roughness"
PBR_CLEARCOAT = "Clearcoat"
PBR_CLEARCOAT_ROUGH = "Clearcoat Roughness"
PBR_CLEARCOAT_GLOSS = "Clearcoat Glossiness"
PBR_EMISSION = "Emission"
PBR_EMISSION_STRENGTH = "Emission Strength"
PBR_SPECULAR = "Specular"
PBR_ALPHA = "Alpha"
PBR_BUMP = "Bump"
PBR_DISPLACEMENT = "Displacement"
ALL_PBR_MODES = ([PBR_DIFFUSE, PBR_METAL, PBR_SSS,PBR_SSS_SCALE, PBR_SSSCOL, PBR_ROUGHNESS, PBR_GLOSSY,
PBR_NORMAL, PBR_TRANSMISSION, PBR_TRANSMISSION_ROUGH, PBR_CLEARCOAT,
PBR_CLEARCOAT_ROUGH, PBR_CLEARCOAT_GLOSS, PBR_EMISSION, PBR_EMISSION_STRENGTH, PBR_SPECULAR, PBR_ALPHA, PBR_BUMP, PBR_DISPLACEMENT])
#Specials names
THICKNESS = "Thickness"
AO = "Ambient Occlusion"
CURVATURE = "Curvature"
COLOURID = "Colour ID"
VERTEXCOL = "Vertex Colour"
LIGHTMAP = "Lightmap"
ALL_SPECIALS = ([THICKNESS, AO, CURVATURE,COLOURID, VERTEXCOL, LIGHTMAP])
COMP_DENOISE_SPECIALS = ([THICKNESS, AO, LIGHTMAP])
COMP_NODENOISE_SPECIALS = ([CURVATURE, VERTEXCOL, COLOURID])
#UVs
NEWUV_SMART_INDIVIDUAL = "SmartUVProject_Individual"
NEWUV_SMART_ATLAS = "SmartUVProject_Atlas"
NEWUV_COMBINE_EXISTING = "CombineExisting"
#Normals
NORMAL_OPENGL = "OpenGL"
NORMAL_DIRECTX = "DirectX"
@classmethod
def get_socket_names(cls):
v = bpy.app.version
if v >= (4,0,0):
return {
cls.PBR_DIFFUSE: "Base Color",
cls.PBR_METAL: "Metallic",
cls.PBR_SSS: "Subsurface Weight",
cls.PBR_SSS_SCALE: "Subsurface Scale",
cls.PBR_SSSCOL: None,
cls.PBR_ROUGHNESS: "Roughness",
cls.PBR_GLOSSY: "Roughness",
cls.PBR_NORMAL: "Normal",
cls.PBR_TRANSMISSION: "Transmission Weight",
cls.PBR_TRANSMISSION_ROUGH: None,
cls.PBR_CLEARCOAT: "Coat Weight",
cls.PBR_CLEARCOAT_ROUGH: "Coat Roughness",
cls.PBR_CLEARCOAT_GLOSS: "Coat Roughness",
cls.PBR_EMISSION: "Emission Color",
cls.PBR_EMISSION_STRENGTH: "Emission Strength",
cls.PBR_SPECULAR: "Specular IOR Level",
cls.PBR_ALPHA: "Alpha"
}
else:
#Principled socket names
return {
cls.PBR_DIFFUSE: "Base Color",
cls.PBR_METAL: "Metallic",
cls.PBR_SSS: "Subsurface",
cls.PBR_SSS_SCALE: "Subsurface Scale",
cls.PBR_SSSCOL: "Subsurface Color",
cls.PBR_ROUGHNESS: "Roughness",
cls.PBR_GLOSSY: "Roughness",
cls.PBR_NORMAL: "Normal",
cls.PBR_TRANSMISSION: "Transmission",
cls.PBR_TRANSMISSION_ROUGH: "Transmission Roughness",
cls.PBR_CLEARCOAT: "Clearcoat",
cls.PBR_CLEARCOAT_ROUGH: "Clearcoat Roughness",
cls.PBR_CLEARCOAT_GLOSS: "Clearcoat Glossiness",
cls.PBR_EMISSION: "Emission",
cls.PBR_EMISSION_STRENGTH: "Emission Strength",
cls.PBR_SPECULAR: "Specular",
cls.PBR_ALPHA: "Alpha"
}
def get_texture_channels(context, tex):
sbp = context.scene.SimpleBake_Props
if sbp.global_mode in [SBConstants.CYCLESBAKE, SBConstants.CYCLESBAKE_S2A]:
return "RGB"
elif tex in [SBConstants.PBR_DIFFUSE, SBConstants.PBR_SSSCOL, SBConstants.PBR_EMISSION, SBConstants.PBR_NORMAL, SBConstants.COLOURID, SBConstants.VERTEXCOL, SBConstants.LIGHTMAP]:
return "RGB"
elif SBConstants.PBRAOVS in tex:
num = int(tex.replace(f"{SBConstants.PBRAOVS}_", ""))
aov = [i for i in sbp.aov_items if i.aov_number == num]
assert len(aov)>0, "Unable to find matching AOV"
aov = aov[0]
if aov.aov_type == "C":
return "RGB"
else:
return "RGB" if sbp.bw_to_rgb else "BW"
else:
return "RGB" if sbp.bw_to_rgb else "BW"
def object_list_to_names_list(context):
sbp = context.scene.SimpleBake_Props
names = sbp.objects_list.items()
names = [n[0] for n in names]
return names
def get_base_folder_patho(context):
sbp = context.scene.SimpleBake_Props
if "--background" in sys.argv or sbp.fake_background:
base_folder = Path(sbp.base_folder_override)
else:
base_folder = Path(bpy.data.filepath).parent
return base_folder
def force_to_object_mode(context):
sbp = context.scene.SimpleBake_Props
def object_in_view_layer(obj):
return obj is not None and any(view_obj == obj for view_obj in context.view_layer.objects)
if not object_in_view_layer(context.active_object):
for item in sbp.objects_list:
obj = item.obj_point
if object_in_view_layer(obj):
context.view_layer.objects.active = obj
break
if context.active_object is None:
for obj in context.view_layer.objects:
if obj.type == "MESH" and obj.visible_get():
context.view_layer.objects.active = obj
break
if context.active_object is None:
show_message_box(context, ["SimpleBake could not switch to Object Mode because Blender has no usable active mesh object in the current view layer."], "Errors occured", icon='ERROR')
return False
if context.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
return context.mode == "OBJECT"
# def check_for_safe_context_class(context):
# sbp = context.scene.SimpleBake_Props
# t = []
# #t.append(base64.b64decode("bGV3aXM=").decode("utf-8"))
# if sbp.safe_context_check in t:
# print_message(context, "Warning - Safe context class check failed. This is not fatal. Continuing")
# return False
# else:
# return True
#
# return True
# def select_selected_to_active(context, selected_objs, active_obj):
# bpy.ops.object.select_all(action="DESELECT")
#
# for obj in selected_objs:
# obj.select_set(state=True)
# active_obj.select_set(state=True)
# context.view_layer.objects.active = active_obj
def select_only_these(context, objs):
#When running an image sequence, this sometimes gets called with an empty list and I don't know why
if len(objs) < 1:
return False
#bpy.ops.object.select_all(action="DESELECT")
[o.select_set(state=False) for o in context.scene.objects]
for obj in objs:
obj.select_set(state=True)
context.view_layer.objects.active = objs[0] #arbitary
def select_only_this(context, obj):
#bpy.ops.object.select_all(action="DESELECT")
[o.select_set(state=False) for o in context.scene.objects]
obj.select_set(state=True)
context.view_layer.objects.active = obj
def pbr_selections_to_list(context):
sbp = context.scene.SimpleBake_Props
selectedbakes = []
selectedbakes.append(SBConstants.PBR_DIFFUSE) if sbp.selected_col else False
selectedbakes.append(SBConstants.PBR_METAL) if sbp.selected_metal else False
selectedbakes.append(SBConstants.PBR_ROUGHNESS) if sbp.selected_rough and sbp.rough_glossy_switch==SBConstants.PBR_ROUGHNESS else False
selectedbakes.append(SBConstants.PBR_GLOSSY) if sbp.selected_rough and sbp.rough_glossy_switch==SBConstants.PBR_GLOSSY else False
selectedbakes.append(SBConstants.PBR_NORMAL) if sbp.selected_normal else False
selectedbakes.append(SBConstants.PBR_TRANSMISSION) if sbp.selected_trans else False
selectedbakes.append(SBConstants.PBR_TRANSMISSION_ROUGH) if sbp.selected_transrough else False
selectedbakes.append(SBConstants.PBR_CLEARCOAT) if sbp.selected_clearcoat else False
selectedbakes.append(SBConstants.PBR_CLEARCOAT_ROUGH) if sbp.selected_clearcoat_rough and sbp.ccrough_glossy_switch==SBConstants.PBR_CLEARCOAT_ROUGH else False
selectedbakes.append(SBConstants.PBR_CLEARCOAT_GLOSS) if sbp.selected_clearcoat_rough and sbp.ccrough_glossy_switch==SBConstants.PBR_CLEARCOAT_GLOSS else False
selectedbakes.append(SBConstants.PBR_EMISSION) if sbp.selected_emission else False
selectedbakes.append(SBConstants.PBR_EMISSION_STRENGTH) if sbp.selected_emission_strength else False
selectedbakes.append(SBConstants.PBR_SPECULAR) if sbp.selected_specular else False
selectedbakes.append(SBConstants.PBR_ALPHA) if sbp.selected_alpha else False
selectedbakes.append(SBConstants.PBR_SSS) if sbp.selected_sss else False
selectedbakes.append(SBConstants.PBR_SSS_SCALE) if sbp.selected_sss_scale else False
selectedbakes.append(SBConstants.PBR_SSSCOL) if sbp.selected_ssscol else False
selectedbakes.append(SBConstants.PBR_BUMP) if sbp.selected_bump else False
selectedbakes.append(SBConstants.PBR_DISPLACEMENT) if sbp.selected_displacement else False
for aov in sbp.aov_items:
if aov.enabled:
selectedbakes.append(SBConstants.PBRAOVS + f"_{aov.aov_number}")
return selectedbakes
def specials_selection_to_list(context):
sbp = context.scene.SimpleBake_Props
selected_specials = []
selected_specials.append(SBConstants.THICKNESS) if sbp.selected_thickness else False
selected_specials.append(SBConstants.COLOURID) if sbp.selected_col_mats else False
selected_specials.append(SBConstants.VERTEXCOL) if sbp.selected_col_vertex else False
selected_specials.append(SBConstants.CURVATURE) if sbp.selected_curvature else False
selected_specials.append(SBConstants.LIGHTMAP) if sbp.selected_lightmap else False
selected_specials.append(SBConstants.AO) if sbp.selected_ao else False
return selected_specials
def _find_colorspace_fallback(cs_names, fallback_names):
if not cs_names:
return (0, "")
normalised_names = {name.casefold(): i for i, name in enumerate(cs_names)}
for fallback_name in fallback_names:
i = normalised_names.get(fallback_name.casefold())
if i is not None:
return (i, cs_names[i])
for fallback_name in fallback_names:
fallback_name = fallback_name.casefold()
for i, cs_name in enumerate(cs_names):
if fallback_name in cs_name.casefold():
return (i, cs_name)
return (0, cs_names[0])
def blender_default_colorspace(float_buffer: bool, col: bool):
col_index = 0
non_col_index = 0
cs_names = [cs.identifier for cs in bpy.types.Image.bl_rna.properties['colorspace_settings'].fixed_type.bl_rna.properties['name'].enum_items]
if bpy.app.version >= (5,0,0):
col_index = 23
non_col_index = 18
elif bpy.app.version >= (4,2,0):
col_index = 20
non_col_index = 17
elif bpy.app.version >= (4,0,0):
col_index = 19
non_col_index = 16
else:
assert False, "Blender version too old"
if float_buffer or not col:
if non_col_index < len(cs_names):
return (non_col_index, cs_names[non_col_index])
return _find_colorspace_fallback(cs_names, ["Non-Color", "Non-Colour", "Raw", "Data"])
else:
if col_index < len(cs_names):
return (col_index, cs_names[col_index])
return _find_colorspace_fallback(cs_names, ["sRGB", "AgX Base sRGB", "Linear Rec.709"])
def get_cs_list(self, context):
cs_items = [(cs.identifier,cs.identifier,"") for cs in bpy.types.Image.bl_rna.properties['colorspace_settings'].fixed_type.bl_rna.properties['name'].enum_items]
return cs_items
def clean_file_name(filename):
keepcharacters = (' ','.','_','~',"-", "[", "]")
return "".join(c for c in filename if c.isalnum() or c in keepcharacters)#.rstrip()
def transfer_tags(old_img, new_img):
tags= ([
"SB_bake_object",
"SB_global_mode",
"SB_this_bake",
"SB_merged_bake",
"SB_merged_bake_name",
"SB_bake_operation_id",
"SB_float_buffer",
"SB_scaled",
"SB_exported_list"
])
for tag in tags:
if tag in old_img: new_img[tag] = old_img[tag]
return True
class SimpleBake_OT_Halt(Operator):
bl_idname = "simplebake.halt"
bl_label = ""
def execute(self, context):
raise Exception
return {'FINISHED'}
def show_message_box(context, messageitems_list, title, icon = 'INFO'):
message=""
for li in messageitems_list:
message+=f"{li}|"
bpy.ops.simplebake.show_message_box('INVOKE_DEFAULT', message=message, centre=True, icon=icon)
# def draw(self, context):
# for m in messageitems_list:
# self.layout.label(text=m)
# context.window_manager.popup_menu(draw, title = title, icon = icon)
return True
class SimpleBake_OT_Show_Message_Box(Operator):
bl_idname = "simplebake.show_message_box"
bl_label = ""
message: StringProperty()
icon: StringProperty(default="INFO")
centre: BoolProperty(default=False)
def execute(self, context):
return {'FINISHED'}
def invoke(self, context, event):
self.restored = False
if self.centre:
self.orig_x = event.mouse_x
self.orig_y = event.mouse_y
w = int(context.window.width/2)
h = int(context.window.height/2)
h = h + (20*len(self.message.split("|")))
context.window.cursor_warp(w, h)
longest = 0
for li in self.message.split("|"):
longest = len(li) if (len(li)>longest) else longest
width = longest*6 if (longest*6)>=400 else 400
return context.window_manager.invoke_props_dialog(self, width = width)
def draw(self, context):
self.layout.label(text="SIMPLEBAKE", icon=self.icon)
message_list = self.message.split("|")
for li in message_list:
row=self.layout.row()
row.scale_y = 0.8
row.alignment = 'LEFT'
row.label(text=li)
if not self.restored and self.centre:
context.window.cursor_warp(self.orig_x, self.orig_y)
self.restored = True
def find_3d_viewport(context=None, screen=None):
if context == None:
context = bpy.context
if screen == None:
screen = bpy.context.window.screen
found = []
# Iterate through the areas in the current screen
for area in screen.areas:
# Check if the area is a 3D viewport
if area.type == 'VIEW_3D':
# Optionally, you can access the space data of the area
for space in area.spaces:
if space.type == 'VIEW_3D':
found.append(space)
return found
def is_blend_saved():
path = bpy.data.filepath
if(path=="/" or path==""):
return False
else:
return True
def is_all_black_or_transparent(img: bpy.types.Image, tol=1e-6):
if not img.has_data:
img.update()
# Blender exposes pixels as RGBA floats in [0..1]; length = w*h*4
px = np.asarray(img.pixels[:], dtype=np.float32).reshape(-1, 4)
rgb = px[:, :3]
a = px[:, 3]
all_black = np.all(np.abs(rgb) <= tol) # R=G=B≈0 everywhere
all_transparent = np.all(a <= tol) # A≈0 everywhere
return all_black, all_transparent
conflicting_addons = []#"ZenUV"]
classes = ([
SimpleBake_OT_Show_Message_Box,
#SimpleBake_OT_Print_Message,
SimpleBake_OT_Halt
])
def register():
global classes
for cls in classes:
register_class(cls)
if _sb_udim_depsgraph_handler not in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.append(_sb_udim_depsgraph_handler)
def unregister():
global classes
for cls in classes:
unregister_class(cls)
if _sb_udim_depsgraph_handler in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.remove(_sb_udim_depsgraph_handler)