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

286 lines
10 KiB
Python

import bpy
from bpy.utils import register_class, unregister_class
from bpy.types import Operator
from bpy.props import FloatProperty, EnumProperty, BoolProperty, StringProperty
from .utils import SBConstants, select_only_this
from .messages import print_message
from .bake_operators.common_bake_support import match_high_low_objects
def rearrange_uvs(obj, uv_name):
uvs = obj.data.uv_layers
orig_active_name = uvs.active.name
orig_active_render_name = [uv.name for uv in uvs if uv.active_render][0]
while uvs[0].name != uv_name:
#Move to bottom
name = uvs[0].name
uvs.active = uvs[0]
uvs.new()
uvs.remove(uvs[0])
uvs[-1].name = name
uvs.active = uvs[orig_active_name]
uvs[orig_active_render_name].active_render = True
def pack_uvs(self, context):
sbp = context.scene.SimpleBake_Props
bpy.ops.uv.select_all(action="SELECT")
#Always average - still relevant for pre-Blender 3.6
if sbp.new_uv_method == "CombineExisting" and self.average_islands:
bpy.ops.uv.average_islands_scale()
n = int(''.join(c for c in bpy.app.version_string if c.isdigit()))
if n >= 360:
bpy.ops.uv.pack_islands("EXEC_DEFAULT", udim_source=sbp.uvp_pack_to, rotate=sbp.uvp_rotate, rotate_method=sbp.uvp_rotation_method, scale=sbp.uvp_scale, merge_overlap=sbp.uvp_merge_overlapping, margin_method=sbp.uvp_margin_method, margin=sbp.uvpackmargin, pin=sbp.uvp_lock_pinned, pin_method=sbp.uvp_lock_method, shape_method=sbp.uvp_shape_method)
else: # Pre Blender 3.6
bpy.ops.uv.pack_islands(rotate=True, margin=sbp.uvpackmargin)
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
class SimpleBake_OT_Process_UVs(Operator):
"""Start the bake operation"""
bl_idname = "simplebake.process_uvs"
bl_description = "Prepare UV maps on the bake objects"
bl_label = "Prepare UVs"
def smart_project_to_atlas(self, context):
sbp = context.scene.SimpleBake_Props
print_message(context, "Smart projecting all objects to atlas map")
#Selection
bpy.ops.object.select_all(action="DESELECT")
for obj in self.bake_objects:
obj.select_set(state=True)
#Make sure we have an active object
if context.active_object == None:
context.view_layer.objects.active = self.bake_objects[0]
for obj in self.bake_objects:
if("SimpleBake" in obj.data.uv_layers):
obj.data.uv_layers.remove(obj.data.uv_layers["SimpleBake"])
obj.data.uv_layers.new(name="SimpleBake")
obj.data.uv_layers["SimpleBake"].active = True
bpy.ops.object.mode_set(mode="EDIT", toggle=False) #Enter edit mode
bpy.ops.mesh.reveal()
bpy.ops.mesh.select_all(action="SELECT")
#Unwrap
bpy.ops.uv.smart_project(angle_limit=self.angle_limit, island_margin=self.island_margin, area_weight=self.area_weight, correct_aspect=self.uvcorrectaspect, scale_to_bounds=self.scale_to_bounds)
#Packing
#pack_uvs(self)
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
return
def combine_existing_to_atlas(self, context):
sbp = context.scene.SimpleBake_Props
print_message(context, "Combining existing UVs into atlas map")
#Selection
# bpy.ops.object.select_all(action="DESELECT")
for obj in bpy.context.selected_objects:
obj.select_set(False)
for obj in self.bake_objects:
obj.select_set(state=True)
#Make sure we have an active object
if context.active_object == None:
context.view_layer.objects.active = self.bake_objects[0]
for obj in self.bake_objects:
if("GENERATED_SimpleBake_Atlas" in obj.data.uv_layers):
obj.data.uv_layers.remove(obj.data.uv_layers["GENERATED_SimpleBake_Atlas"])
u = obj.data.uv_layers.new(name="GENERATED_SimpleBake_Atlas")
u.active = True
#With everything selected, pack into one big map
bpy.ops.object.mode_set(mode="EDIT", toggle=False)
bpy.ops.mesh.reveal()
bpy.ops.mesh.select_all(action="SELECT")
pack_uvs(self, context)
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
return
def smart_project_individual_objects(self, context):
print_message(context, "Smart projecting each object individually")
for obj in self.bake_objects:
select_only_this(context, obj)
if("SimpleBake" in obj.data.uv_layers):
obj.data.uv_layers.remove(obj.data.uv_layers["SimpleBake"])
obj.data.uv_layers.new(name="SimpleBake")
obj.data.uv_layers["SimpleBake"].active = True
bpy.ops.object.mode_set(mode="EDIT", toggle=False)
bpy.ops.mesh.reveal()
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.uv.smart_project(angle_limit=self.angle_limit, island_margin=self.island_margin, area_weight=self.area_weight, correct_aspect=self.uvcorrectaspect, scale_to_bounds=self.scale_to_bounds)
bpy.ops.object.mode_set(mode="OBJECT", toggle=False)
return
def per_mat_pack_uvs_to_bounds(self, context):
sbp = context.scene.SimpleBake_Props
print_message(context, "Packing UVs for each material into new UV map")
bpy.ops.object.select_all(action="DESELECT")
for obj in self.bake_objects:
# Copy existing UV data into a fresh SimpleBake layer
source_layer = obj.data.uv_layers.active
if "SimpleBake" in obj.data.uv_layers:
obj.data.uv_layers.remove(obj.data.uv_layers["SimpleBake"])
new_layer = obj.data.uv_layers.new(name="SimpleBake")
if source_layer:
for i, item in enumerate(source_layer.data):
new_layer.data[i].uv = item.uv.copy()
new_layer.active = True
select_only_this(context, obj)
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bpy.ops.mesh.reveal()
# Get enabled materials — strip SBM_/SBW_ prefix for comparison
enabled_mat_names = {
e.mat_name for e in sbp.mat_bake_list
if e.obj_name == obj.name and e.enabled
}
i = 0
for slot in obj.material_slots:
if slot.material is None:
i += 1
continue
mat_name = slot.material.name
if mat_name.startswith("SBW_"): mat_name = mat_name[4:]
if mat_name.startswith("SBM_"): mat_name = mat_name[4:]
if enabled_mat_names and mat_name not in enabled_mat_names:
i += 1
continue
obj.active_material_index = i
bpy.ops.mesh.select_all(action="DESELECT")
bpy.ops.object.material_slot_select()
bpy.ops.uv.pack_islands(scale=True, margin=self.island_margin)
i += 1
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
return
def check_prefer_sb_maps(self, context):
if self.prefer_sb_uv_maps:
print_message(context, "Preferring existing UV maps called SimpleBake. Setting them to active")
for obj in self.bake_objects:
if("SimpleBake" in obj.data.uv_layers):
obj.data.uv_layers["SimpleBake"].active = True
return
def execute(self, context):
sbp = context.scene.SimpleBake_Props
#Pull what we need from the panel
self.island_margin = sbp.unwrapmargin
self.uvcorrectaspect = sbp.uvcorrectaspect
self.angle_limit = sbp.angle_limit
self.area_weight = sbp.area_weight
self.scale_to_bounds = sbp.scale_to_bounds
self.average_islands = sbp.average_uv_size
self.prefer_sb_uv_maps = sbp.prefer_existing_sbmap
self.new_uv_option = sbp.new_uv_option
self.new_uv_method = sbp.new_uv_method
self.expand_mat_uvs = sbp.expand_mat_uvs
self.uvpackmargin = sbp.uvpackmargin
#Set the bake 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 bo
hl_matches = bo.hl_matches
self.bake_objects = [context.scene.objects[name] for name in hl_matches.keys()]
elif sbp.selected_s2a: #Covering decals or standard
self.bake_objects =[sbp.targetobj]
elif sbp.cycles_s2a:
self.bake_objects =[sbp.targetobj_cycles]
else:
self.bake_objects = [i.obj_point for i in sbp.objects_list]
#Decide what we are actually doing
if sbp.tex_per_mat and self.expand_mat_uvs:
self.per_mat_pack_uvs_to_bounds(context)
elif self.new_uv_option and len(self.bake_objects)==1:
self.smart_project_individual_objects(context)
elif self.new_uv_option and self.new_uv_method == SBConstants.NEWUV_SMART_INDIVIDUAL:
self.smart_project_individual_objects(context)
elif self.new_uv_option and self.new_uv_method == SBConstants.NEWUV_SMART_ATLAS:
self.smart_project_to_atlas(context)
elif self.new_uv_option and self.new_uv_method == SBConstants.NEWUV_COMBINE_EXISTING:
self.combine_existing_to_atlas(context)
elif not self.new_uv_option:
print_message(context, "We are working with existing UV maps")
self.check_prefer_sb_maps(context)
else:
print_message(context, "Something went wrong processing UVs")
for obj in self.bake_objects:
obj["SB_uv_used_for_bake"] = obj.data.uv_layers.active.name
if self.new_uv_option and sbp.move_new_uvs_to_top:
rearrange_uvs(obj, obj["SB_uv_used_for_bake"])
return {'FINISHED'}
classes = ([
SimpleBake_OT_Process_UVs#,
#SimpleBake_OT_Pack_UVs
])
def register():
global classes
for cls in classes:
register_class(cls)
def unregister():
global classes
for cls in classes:
unregister_class(cls)