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

583 lines
19 KiB
Python

import bpy
from ..utils import find_closest_obj, show_message_box, find_3d_viewport
from bpy.utils import register_class, unregister_class
from bpy.types import Operator, PropertyGroup, UIList
from bpy.props import EnumProperty, StringProperty
from ..utils import invalidate_udim_cache
def refresh_bake_objects_list(context):
sbp = context.scene.SimpleBake_Props
if sbp.global_mode == "PBR" and sbp.selected_s2a:
if sbp.s2a_opmode != "automatch":
if sbp.targetobj != None:
if sbp.targetobj.name not in bpy.context.scene.objects:
sbp.targetobj = None
if sbp.global_mode == "CyclesBake" and sbp.cycles_s2a:
if sbp.s2a_opmode != "automatch":
if sbp.targetobj_cycles != None:
if sbp.targetobj_cycles.name not in bpy.context.scene.objects:
sbp.targetobj_cycles = None
objects_list = sbp.objects_list
gone = []
for li in objects_list:
#Is it empty?
if li.obj_point == None:
gone.append(li.name)
#Is it no longer in the scene?
elif li.obj_point.name not in context.scene.objects:
gone.append(li.name)
#Is it not in use anywhere else?
elif len(li.obj_point.users_scene) < 1:
gone.append(li.name)
elif sbp.selected_s2a or sbp.cycles_s2a:
if sbp.s2a_opmode=="automatch" and sbp.auto_match_mode == "name":
for o in sbp.objects_list:
if o.obj_point != None and not o.obj_point.name.lower().endswith("_high"):
gone.append(o.name)
for g in gone:
objects_list.remove(objects_list.find(g))
for i in objects_list:
i.name = i.obj_point.name
#Throw in a refresh of the UV list. WHy not?
bpy.ops.simplebake.sync_uv_list()
def _strip_sb_prefixes(name):
"""Strip SimpleBake backup prefixes (SBW_, SBM_) to recover the original material name."""
if name.startswith("SBW_"):
name = name[4:]
if name.startswith("SBM_"):
name = name[4:]
return name
def refresh_mat_bake_list(context):
"""Keep mat_bake_list in sync with objects_list and their materials."""
sbp = context.scene.SimpleBake_Props
# Build the set of (obj_name, mat_name) pairs that should exist.
# Strip SimpleBake backup prefixes so the list always stores original names.
wanted = set()
for item in sbp.objects_list:
if item.obj_point is None:
continue
obj = item.obj_point
for slot in obj.material_slots:
if slot.material is not None:
wanted.add((obj.name, _strip_sb_prefixes(slot.material.name)))
# Remove stale entries
to_remove = []
for i, entry in enumerate(sbp.mat_bake_list):
if (entry.obj_name, entry.mat_name) not in wanted:
to_remove.append(i)
for i in reversed(to_remove):
sbp.mat_bake_list.remove(i)
# Add missing entries (default enabled=True)
existing = {(e.obj_name, e.mat_name) for e in sbp.mat_bake_list}
for obj_name, mat_name in sorted(wanted):
if (obj_name, mat_name) not in existing:
entry = sbp.mat_bake_list.add()
entry.obj_name = obj_name
entry.mat_name = mat_name
entry.enabled = True
class SIMPLEBAKE_UL_Objects_List(UIList):
"""Bake objects list"""
viable_high_low_bakes = []
def draw_item(self, context, layout, data, item, icon, active_data,
active_propname, index):
sbp = context.scene.SimpleBake_Props
custom_icon = 'OBJECT_DATAMODE'
name = item.obj_point.name if item.obj_point != None else "_?_"
target_obj = "???"
cage_obj = ""
#Set the icon if in name mode
if (sbp.selected_s2a or sbp.cycles_s2a) and item.obj_point != None:
if sbp.s2a_opmode=="automatch":
if sbp.auto_match_mode == "name":
custom_icon = "SEQUENCE_COLOR_01" if bpy.app.version < (4,4,0) else "STRIP_COLOR_01"
target_obj = "???"
for o in context.scene.objects:
if name.lower().replace("_high", "_low") == o.name.lower():
target_obj = o.name
custom_icon = "SEQUENCE_COLOR_04" if bpy.app.version < (4,4,0) else "STRIP_COLOR_04"
if name not in __class__.viable_high_low_bakes:
__class__.viable_high_low_bakes.append(name)
#We didn't find a match
if target_obj == "???":
if name in __class__.viable_high_low_bakes:
__class__.viable_high_low_bakes.remove(name)
# Look up cage object by naming convention
cage_obj = ""
if target_obj != "???":
parts = target_obj.split("_")
base_name = '_'.join(parts[:-1]).lower()
for o in context.scene.objects:
if o.name.lower() == f"{base_name}_cage":
cage_obj = o.name
break
#Set the icon if in position mode
if (sbp.selected_s2a or sbp.cycles_s2a) and item.obj_point != None:
if sbp.s2a_opmode=="automatch":
if sbp.auto_match_mode == "position":
co = find_closest_obj(context, name)
if co != None:
custom_icon = "SEQUENCE_COLOR_04" if bpy.app.version < (4,4,0) else "STRIP_COLOR_04"
target_obj = co.name
if name not in __class__.viable_high_low_bakes:
__class__.viable_high_low_bakes.append(name)
else:
custom_icon = "SEQUENCE_COLOR_01" if bpy.app.version < (4,4,0) else "STRIP_COLOR_01"
target_obj = "???"
if name in __class__.viable_high_low_bakes:
__class__.viable_high_low_bakes.remove(name)
# Draw
if self.layout_type in {'DEFAULT', 'COMPACT'}:
col = layout.column()
col.label(text=name, icon=custom_icon)
if (sbp.selected_s2a or sbp.cycles_s2a) and sbp.s2a_opmode=="automatch":
col = layout.column()
col.alignment = 'CENTER'
col.label(text=" --> ")
col = layout.column()
icon = "QUESTION" if target_obj == "???" else "CHECKMARK"
col.label(text=target_obj, icon=icon)
if sbp.auto_match_mode == "name":
col = layout.column()
if cage_obj:
col.label(text=cage_obj, icon="MOD_MESHDEFORM")
else:
col.label(text="No cage")
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label(text="", icon = custom_icon)
class SimpleBake_OT_Add_Bake_Object(Operator):
"""Add selected object(s) to the bake list"""
bl_idname = "simplebake.add_bake_object"
bl_label = "Adds a bake object to the list"
@classmethod
def poll(cls, context):
return len(context.selected_objects)
def execute(self, context):
sbp = context.scene.SimpleBake_Props
#Lets get rid of the non-mesh objects from the selections
[obj.select_set(False) for obj in context.scene.objects if obj.type != "MESH"]
#Do we still have an active object?
if context.active_object == None:
#If not, pick one
context.view_layer.objects.active = context.selected_objects[0]
#Add to list if not already in the list
objs = context.selected_objects.copy()
for obj in objs:
#If we are in matching high to low mode, only allow adding of _high objects
if (sbp.selected_s2a or sbp.cycles_s2a):
if sbp.s2a_opmode=="automatch" and sbp.auto_match_mode == "name":
if not obj.name.lower().endswith("_high"):
messages = ([
f"ERROR: Can't add object {obj.name}",
"You have auto match high and low poly selected and",
"you are using name mode.",
"Only object names ending in \"_high\" can be added"
])
show_message_box(context, messages, "Errors occured", icon = 'ERROR')
continue
#Only add if not already on the list
r = [o.name for o in sbp.objects_list if o.name == obj.name]
if len(r) == 0:
n = sbp.objects_list.add()
n.obj_point = obj
n.name = obj.name
#Throw in a refresh
refresh_bake_objects_list(context)
refresh_mat_bake_list(context)
if (sbp.selected_s2a or sbp.cycles_s2a) and sbp.s2a_opmode=="automatch":
if len(SIMPLEBAKE_UL_Objects_List.viable_high_low_bakes)<2:
sbp.merged_bake = False
#Invalidate UDIM cache
invalidate_udim_cache()
return{'FINISHED'}
class SimpleBake_OT_Add_Bake_Object_By_Name(Operator):
"""Add specified object(s) to the bake list"""
bl_idname = "simplebake.add_bake_object_by_name"
bl_label = "Adds a bake object to the list"
override_target_obj_name: StringProperty(default="")
def execute(self, context):
sbp = context.scene.SimpleBake_Props
n = sbp.objects_list.add()
obj = context.scene.objects[self.override_target_obj_name]
n.obj_point = obj
n.name = obj.name
#Invalidate UDIM cache
invalidate_udim_cache()
return{'FINISHED'}
class SimpleBake_OT_Remove_Bake_Object(Operator):
"""Remove the selected object from the bake list."""
bl_idname = "simplebake.remove_bake_object"
bl_label = "Removes a bake object from the list"
@classmethod
def poll(cls, context):
#TODO-------
return context.scene.SimpleBake_Props.objects_list
def execute(self, context):
sbp = context.scene.SimpleBake_Props
objects_list = sbp.objects_list
index = sbp.objects_list_index
#Record it's name
name = sbp.objects_list[index].name
objects_list.remove(index)
sbp.objects_list_index = min(max(0, index - 1), len(objects_list) - 1)
#Throw in a refresh
refresh_bake_objects_list(context)
refresh_mat_bake_list(context)
#Also remove from high low list
if name in SIMPLEBAKE_UL_Objects_List.viable_high_low_bakes:
SIMPLEBAKE_UL_Objects_List.viable_high_low_bakes.remove(name)
if (sbp.selected_s2a or sbp.cycles_s2a) and sbp.s2a_opmode=="automatch":
if len(SIMPLEBAKE_UL_Objects_List.viable_high_low_bakes)<2:
sbp.merged_bake = False
#Invalidate UDIM cache
invalidate_udim_cache()
return{'FINISHED'}
class SimpleBake_OT_Clear_Bake_Objects_List(Operator):
"""Clear the object list"""
bl_idname = "simplebake.clear_bake_objects_list"
bl_label = "Clears the bake object list"
@classmethod
def poll(cls, context):
return True
def execute(self, context):
sbp = context.scene.SimpleBake_Props
sbp.objects_list.clear()
#Throw in a refresh
refresh_bake_objects_list(context)
refresh_mat_bake_list(context)
#Clear the high low match list
SIMPLEBAKE_UL_Objects_List.viable_high_low_bakes = []
# if len(SIMPLEBAKE_UL_Objects_List.viable_high_low_bakes)<2:
# sbp.merged_bake = False
#Invalidate UDIM cache
invalidate_udim_cache()
return{'FINISHED'}
class SimpleBake_OT_Move_Bake_Object_List(Operator):
"""Move an object in the list."""
bl_idname = "simplebake.move_bake_object_list"
bl_label = "Moves an item in the bake objects list"
direction: bpy.props.EnumProperty(items=(('UP', 'Up', ""),
('DOWN', 'Down', ""),))
@classmethod
def poll(cls, context):
return context.scene.SimpleBake_Props.objects_list
def move_index(self, context):
""" Move index of an item render queue while clamping it. """
sbp = context.scene.SimpleBake_Props
index = sbp.objects_list_index
list_length = len(sbp.objects_list) - 1 # (index starts at 0)
new_index = index + (-1 if self.direction == 'UP' else 1)
sbp.objects_list_index = max(0, min(new_index, list_length))
def execute(self, context):
sbp = context.scene.SimpleBake_Props
objects_list = sbp.objects_list
index = sbp.objects_list_index
neighbor = index + (-1 if self.direction == 'UP' else 1)
objects_list.move(neighbor, index)
self.move_index(context)
#Throw in a refresh
refresh_bake_objects_list(context)
return{'FINISHED'}
class SimpleBake_OT_Refresh_Bake_Object_List(Operator):
"""Refresh the list to remove objects"""
bl_idname = "simplebake.refresh_bake_object_list"
bl_label = "Refresh the bake objects list"
@classmethod
def poll(cls, context):
return True
def execute(self, context):
refresh_bake_objects_list(context)
refresh_mat_bake_list(context)
#Invalidate UDIM cache
invalidate_udim_cache()
return{'FINISHED'}
#highlight_on = False
class SimpleBake_OT_Set_Highlight_Cols(Operator):
"""Actually modify the object colours"""
bl_idname = "simplebake.set_highlight_cols"
bl_label = "Set highlight cols to objects"
@classmethod
def update_object_list_from_selectd(cls):
sbp = bpy.context.scene.SimpleBake_Props
if (o:=bpy.context.active_object):
if (i:=sbp.objects_list.find(o.name))!=-1:
if sbp.objects_list_index !=i:
sbp.objects_list_index =i
def execute(self, context):
sbp = bpy.context.scene.SimpleBake_Props
#Do this anyway (EEVEE updates even if object selection changed)
__class__.update_object_list_from_selectd()
if not sbp.highlight_on:
return{'CANCELLED'} #Highlight turned off - end timer
#Do nothing more if we aren't in shading mode
any_shading = False
for space in find_3d_viewport():
if space.shading.type == "SOLID":
any_shading = True
if not any_shading:
return{'CANCELLED'}
#return 0.2 #Do nothing. Check back later
def set_col(obj_names, col):
for o_name in obj_names:
if (o:=bpy.data.objects.get(o_name)):
if o.color != col:
if "SB_orig_col" not in o:
o["SB_orig_col"] = o.color
o.color = col
bake_objs = [o.name for o in sbp.objects_list]
all_objs = ([o.name for o in bpy.context.scene.objects if
o.name not in bake_objs and
o.type == "MESH" and
"SB_auto_cage" not in o])
set_col(all_objs, (1,1,1,1))
c = sbp.highlight_col
set_col(bake_objs, (c.r, c.g, c.b, 1.0))
return{'FINISHED'}
class SimpleBake_OT_Highlight_Bake_Objects(Operator):
"""Highlight selected objects in the viewport"""
bl_idname = "simplebake.highlight_bake_objects"
bl_label = "Highlights bake objects in the viewport on this screen"
@classmethod
def poll(cls, context):
sbp = context.scene.SimpleBake_Props
return not sbp.highlight_on
@classmethod
def set_timer(cls):
bpy.app.timers.register(cls.col_bake_objects, first_interval=1.0)
@classmethod
def col_bake_objects(cls):
sbp = bpy.context.scene.SimpleBake_Props
bpy.ops.simplebake.set_highlight_cols()
if not sbp.highlight_on:
return None
else:
return 0.2
@classmethod
def set_display(cls, context):
for space in find_3d_viewport():
space.shading.color_type = "OBJECT"
def execute(self, context):
sbp = context.scene.SimpleBake_Props
sbp.highlight_on = True
__class__.set_timer()
__class__.col_bake_objects()
__class__.set_display(context)
return{'FINISHED'}
class SimpleBake_OT_Remove_Highlight(Operator):
"""Remove highlight of selected objects in the viewport"""
bl_idname = "simplebake.remove_highlight"
bl_label = "Removes the highlights for bake objects in the viewport on this screen"
@classmethod
def poll(cls, context):
sbp = context.scene.SimpleBake_Props
return sbp.highlight_on
def uncol_bake_objects(self, context):
sbp = context.scene.SimpleBake_Props
bpy.ops.simplebake.refresh_bake_object_list()
obj_names = [o.name for o in sbp.objects_list]
for o_name in obj_names:
if (o:=bpy.data.objects.get(o_name)):
if "SB_orig_col" in o:
o.color = o["SB_orig_col"]
del o["SB_orig_col"]
def unset_display(self, context):
#for screen in bpy.data.screens:
screen = bpy.context.window.screen
# 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':
space.shading.color_type = "MATERIAL"
def execute(self, context):
sbp = context.scene.SimpleBake_Props
sbp.highlight_on = False
self.uncol_bake_objects(context)
self.unset_display(context)
return{'FINISHED'}
class SimpleBake_OT_Materials_Expand_All(Operator):
"""Expand all objects in the Materials section"""
bl_idname = "simplebake.materials_expand_all"
bl_label = "Expand All"
def execute(self, context):
for item in context.scene.SimpleBake_Props.objects_list:
item.expanded = True
return {'FINISHED'}
class SimpleBake_OT_Materials_Collapse_All(Operator):
"""Collapse all objects in the Materials section"""
bl_idname = "simplebake.materials_collapse_all"
bl_label = "Collapse All"
def execute(self, context):
for item in context.scene.SimpleBake_Props.objects_list:
item.expanded = False
return {'FINISHED'}
classes = ([
SIMPLEBAKE_UL_Objects_List,
SimpleBake_OT_Add_Bake_Object,
SimpleBake_OT_Add_Bake_Object_By_Name,
SimpleBake_OT_Remove_Bake_Object,
SimpleBake_OT_Clear_Bake_Objects_List,
SimpleBake_OT_Move_Bake_Object_List,
SimpleBake_OT_Refresh_Bake_Object_List,
SimpleBake_OT_Highlight_Bake_Objects,
SimpleBake_OT_Remove_Highlight,
SimpleBake_OT_Set_Highlight_Cols,
SimpleBake_OT_Materials_Expand_All,
SimpleBake_OT_Materials_Collapse_All,
])
def register():
global classes
for cls in classes:
register_class(cls)
def unregister():
global classes
for cls in classes:
unregister_class(cls)