UI Improvements for Atlasing
Made some UI imprvoements to the Atlas system, uses icons, able to collapse materials to prevent a huge long list and more.
This commit is contained in:
@@ -41,6 +41,12 @@ def register() -> None:
|
|||||||
default=False
|
default=False
|
||||||
)))
|
)))
|
||||||
|
|
||||||
|
register_property((bpy.types.Scene, "material_search_filter", bpy.props.StringProperty(
|
||||||
|
name="Search Materials",
|
||||||
|
description="Filter materials by name",
|
||||||
|
default=""
|
||||||
|
)))
|
||||||
|
|
||||||
register_property((bpy.types.Material, "include_in_atlas", bpy.props.BoolProperty(
|
register_property((bpy.types.Material, "include_in_atlas", bpy.props.BoolProperty(
|
||||||
name=t("TextureAtlas.include_in_atlas"),
|
name=t("TextureAtlas.include_in_atlas"),
|
||||||
description=t("TextureAtlas.include_in_atlas_desc"),
|
description=t("TextureAtlas.include_in_atlas_desc"),
|
||||||
|
|||||||
@@ -25,15 +25,20 @@ class MaterialImageList:
|
|||||||
self.fit = None
|
self.fit = None
|
||||||
|
|
||||||
def scale_images_to_largest(images: list[Image]) -> set:
|
def scale_images_to_largest(images: list[Image]) -> set:
|
||||||
print([image.name for image in images])
|
|
||||||
x: int = 0
|
x: int = 0
|
||||||
y: int = 0
|
y: int = 0
|
||||||
for image in images:
|
|
||||||
|
# Filter out None or invalid images
|
||||||
|
valid_images = [img for img in images if img and img.has_data]
|
||||||
|
|
||||||
|
if not valid_images:
|
||||||
|
return 0, 0
|
||||||
|
|
||||||
|
for image in valid_images:
|
||||||
x = max(x, image.size[0])
|
x = max(x, image.size[0])
|
||||||
y = max(y, image.size[1])
|
y = max(y, image.size[1])
|
||||||
print(x,y)
|
|
||||||
|
|
||||||
for image in images:
|
for image in valid_images:
|
||||||
image.scale(width=int(x), height=int(y))
|
image.scale(width=int(x), height=int(y))
|
||||||
|
|
||||||
return x, y
|
return x, y
|
||||||
@@ -57,7 +62,8 @@ def get_material_images_from_scene(context: Context) -> list[MaterialImageList]:
|
|||||||
for obj in context.scene.objects:
|
for obj in context.scene.objects:
|
||||||
if obj.type == 'MESH':
|
if obj.type == 'MESH':
|
||||||
for mat_slot in obj.material_slots:
|
for mat_slot in obj.material_slots:
|
||||||
if mat_slot.material and mat_slot.material.include_in_atlas:
|
# Only process materials that are selected for atlas
|
||||||
|
if mat_slot.material and mat_slot.material.include_in_atlas is True:
|
||||||
new_mat_image_item = MaterialImageList()
|
new_mat_image_item = MaterialImageList()
|
||||||
try:
|
try:
|
||||||
new_mat_image_item.albedo = bpy.data.images[mat_slot.material.texture_atlas_albedo]
|
new_mat_image_item.albedo = bpy.data.images[mat_slot.material.texture_atlas_albedo]
|
||||||
@@ -114,6 +120,7 @@ def get_material_images_from_scene(context: Context) -> list[MaterialImageList]:
|
|||||||
|
|
||||||
return material_image_list
|
return material_image_list
|
||||||
|
|
||||||
|
|
||||||
def prep_images_in_scene(context: Context) -> list[MaterialImageList]:
|
def prep_images_in_scene(context: Context) -> list[MaterialImageList]:
|
||||||
preped_images: list[MaterialImageList] = get_material_images_from_scene(context)
|
preped_images: list[MaterialImageList] = get_material_images_from_scene(context)
|
||||||
for MaterialImageClass in preped_images:
|
for MaterialImageClass in preped_images:
|
||||||
@@ -141,14 +148,14 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
|
|||||||
|
|
||||||
def execute(self, context: Context) -> set:
|
def execute(self, context: Context) -> set:
|
||||||
try:
|
try:
|
||||||
# Only get materials marked for atlas creation
|
# Get only materials that are explicitly marked for inclusion
|
||||||
mat_images: list[MaterialImageList] = [m for m in prep_images_in_scene(context) if m.material.include_in_atlas]
|
selected_materials = [m for m in prep_images_in_scene(context) if m.material and m.material.include_in_atlas is True]
|
||||||
|
|
||||||
if not mat_images:
|
if not selected_materials:
|
||||||
self.report({'WARNING'}, t("TextureAtlas.no_materials_selected"))
|
self.report({'WARNING'}, t("TextureAtlas.no_materials_selected"))
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
packer: BinPacker = BinPacker(mat_images)
|
packer: BinPacker = BinPacker(selected_materials)
|
||||||
mat_images = packer.fit()
|
mat_images = packer.fit()
|
||||||
|
|
||||||
size: list[int] = [max([matimg.fit.w + matimg.albedo.size[0] for matimg in mat_images]),
|
size: list[int] = [max([matimg.fit.w + matimg.albedo.size[0] for matimg in mat_images]),
|
||||||
@@ -274,13 +281,13 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
|
|||||||
atlased_mat.material.node_tree.links.new(output_node.inputs["Surface"], principled_node.outputs["BSDF"])
|
atlased_mat.material.node_tree.links.new(output_node.inputs["Surface"], principled_node.outputs["BSDF"])
|
||||||
atlased_mat.material.node_tree.links.new(normal_map_node.inputs["Color"], normal_node.outputs["Color"])
|
atlased_mat.material.node_tree.links.new(normal_map_node.inputs["Color"], normal_node.outputs["Color"])
|
||||||
|
|
||||||
# Only update materials for meshes that had materials included in the atlas
|
# Only update selected materials for meshes
|
||||||
for obj in context.scene.objects:
|
for obj in context.scene.objects:
|
||||||
if obj.type == 'MESH':
|
if obj.type == 'MESH':
|
||||||
if any(mat_slot.material and mat_slot.material.include_in_atlas for mat_slot in obj.material_slots):
|
|
||||||
mesh: Mesh = obj.data
|
mesh: Mesh = obj.data
|
||||||
mesh.materials.clear()
|
for i, mat_slot in enumerate(obj.material_slots):
|
||||||
mesh.materials.append(atlased_mat.material)
|
if mat_slot.material and mat_slot.material.include_in_atlas is True:
|
||||||
|
mesh.materials[i] = atlased_mat.material
|
||||||
|
|
||||||
self.report({'INFO'}, t("TextureAtlas.atlas_completed"))
|
self.report({'INFO'}, t("TextureAtlas.atlas_completed"))
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
+99
-11
@@ -1,11 +1,56 @@
|
|||||||
from bpy.types import UIList, Panel, UILayout, Object, Context,Material, Operator
|
from bpy.types import UIList, Panel, UILayout, Object, Context,Material, Operator
|
||||||
import bpy
|
import bpy
|
||||||
|
from math import sqrt
|
||||||
from ..core.register import register_wrap
|
from ..core.register import register_wrap
|
||||||
from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
|
||||||
from ..core.common import SceneMatClass, MaterialListBool, get_selected_armature
|
from ..core.common import SceneMatClass, MaterialListBool, get_selected_armature
|
||||||
from ..functions.atlas_materials import AvatarToolKit_OT_AtlasMaterials
|
from ..functions.atlas_materials import AvatarToolKit_OT_AtlasMaterials
|
||||||
from ..functions.translations import t
|
from ..functions.translations import t
|
||||||
|
|
||||||
|
@register_wrap
|
||||||
|
class AvatarToolKit_OT_SelectAllMaterials(Operator):
|
||||||
|
bl_idname = 'avatar_toolkit.select_all_materials'
|
||||||
|
bl_label = "Select All"
|
||||||
|
bl_description = "Select all materials for atlas"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
for item in context.scene.materials:
|
||||||
|
item.mat.include_in_atlas = True
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
@register_wrap
|
||||||
|
class AvatarToolKit_OT_SelectNoneMaterials(Operator):
|
||||||
|
bl_idname = 'avatar_toolkit.select_none_materials'
|
||||||
|
bl_label = "Select None"
|
||||||
|
bl_description = "Deselect all materials"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
for item in context.scene.materials:
|
||||||
|
item.mat.include_in_atlas = False
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
@register_wrap
|
||||||
|
class AvatarToolKit_OT_ExpandAllMaterials(Operator):
|
||||||
|
bl_idname = 'avatar_toolkit.expand_all_materials'
|
||||||
|
bl_label = "Expand All"
|
||||||
|
bl_description = "Expand all material settings"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
for item in context.scene.materials:
|
||||||
|
item.mat.material_expanded = True
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
@register_wrap
|
||||||
|
class AvatarToolKit_OT_CollapseAllMaterials(Operator):
|
||||||
|
bl_idname = 'avatar_toolkit.collapse_all_materials'
|
||||||
|
bl_label = "Collapse All"
|
||||||
|
bl_description = "Collapse all material settings"
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
for item in context.scene.materials:
|
||||||
|
item.mat.material_expanded = False
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
@register_wrap
|
@register_wrap
|
||||||
class AvatarToolKit_OT_ExpandSectionMaterials(Operator):
|
class AvatarToolKit_OT_ExpandSectionMaterials(Operator):
|
||||||
bl_idname = 'avatar_toolkit.expand_section_materials'
|
bl_idname = 'avatar_toolkit.expand_section_materials'
|
||||||
@@ -40,26 +85,70 @@ class AvatarToolKit_UL_MaterialTextureAtlasProperties(UIList):
|
|||||||
bl_space_type = 'VIEW_3D'
|
bl_space_type = 'VIEW_3D'
|
||||||
bl_region_type = 'UI'
|
bl_region_type = 'UI'
|
||||||
|
|
||||||
def draw_item(self, context: Context, layout: UILayout, data: Object, item: SceneMatClass, icon, active_data, active_propname, index):
|
def draw_header(self, context):
|
||||||
if context.scene.texture_atlas_Has_Mat_List_Shown:
|
layout = self.layout
|
||||||
|
row = layout.row(align=True)
|
||||||
|
|
||||||
|
row.operator("avatar_toolkit.select_all_materials", text="", icon='CHECKBOX_HLT')
|
||||||
|
row.operator("avatar_toolkit.select_none_materials", text="", icon='CHECKBOX_DEHLT')
|
||||||
|
row.operator("avatar_toolkit.expand_all_materials", text="", icon='DISCLOSURE_TRI_DOWN')
|
||||||
|
row.operator("avatar_toolkit.collapse_all_materials", text="", icon='DISCLOSURE_TRI_RIGHT')
|
||||||
|
row.prop(context.scene, "material_search_filter", text="", icon='VIEWZOOM')
|
||||||
|
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
row = box.row()
|
row = box.row()
|
||||||
|
row.label(text=f"Estimated Atlas Size: {self.calculate_atlas_size(context)}px")
|
||||||
|
|
||||||
# Draw material entry
|
def draw_item(self, context: Context, layout: UILayout, data: Object, item: SceneMatClass, icon, active_data, active_propname, index):
|
||||||
|
if context.scene.texture_atlas_Has_Mat_List_Shown:
|
||||||
|
if context.scene.material_search_filter and context.scene.material_search_filter.lower() not in item.mat.name.lower():
|
||||||
|
return
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
|
||||||
|
# Add a clear checkbox for material selection
|
||||||
|
row.prop(item.mat, "include_in_atlas", text="", icon='CHECKBOX_HLT' if item.mat.include_in_atlas else 'CHECKBOX_DEHLT')
|
||||||
|
|
||||||
|
# Material name and expansion toggle
|
||||||
row.prop(item.mat, "material_expanded",
|
row.prop(item.mat, "material_expanded",
|
||||||
text=item.mat.name,
|
text=item.mat.name,
|
||||||
icon='DOWNARROW_HLT' if item.mat.material_expanded else 'RIGHTARROW',
|
icon='DOWNARROW_HLT' if item.mat.material_expanded else 'RIGHTARROW',
|
||||||
emboss=False)
|
emboss=False)
|
||||||
row.prop(item.mat, "include_in_atlas", text="")
|
|
||||||
|
|
||||||
|
# Show texture settings if expanded
|
||||||
if item.mat.material_expanded and item.mat.include_in_atlas:
|
if item.mat.material_expanded and item.mat.include_in_atlas:
|
||||||
|
box = layout.box()
|
||||||
col = box.column(align=True)
|
col = box.column(align=True)
|
||||||
col.prop(item.mat, "texture_atlas_albedo")
|
self.draw_texture_row(col, item.mat, "texture_atlas_albedo", "IMAGE_RGB")
|
||||||
col.prop(item.mat, "texture_atlas_normal")
|
self.draw_texture_row(col, item.mat, "texture_atlas_normal", "NORMALS_FACE")
|
||||||
col.prop(item.mat, "texture_atlas_emission")
|
self.draw_texture_row(col, item.mat, "texture_atlas_emission", "LIGHT")
|
||||||
col.prop(item.mat, "texture_atlas_ambient_occlusion")
|
self.draw_texture_row(col, item.mat, "texture_atlas_ambient_occlusion", "SHADING_SOLID")
|
||||||
col.prop(item.mat, "texture_atlas_height")
|
self.draw_texture_row(col, item.mat, "texture_atlas_height", "IMAGE_ZDEPTH")
|
||||||
col.prop(item.mat, "texture_atlas_roughness")
|
self.draw_texture_row(col, item.mat, "texture_atlas_roughness", "MATERIAL")
|
||||||
|
|
||||||
|
col.separator(factor=0.5)
|
||||||
|
|
||||||
|
def draw_texture_row(self, layout, material, prop_name, icon):
|
||||||
|
row = layout.row()
|
||||||
|
row.prop(material, prop_name, icon=icon)
|
||||||
|
if getattr(material, prop_name):
|
||||||
|
row.label(text="", icon='CHECKMARK')
|
||||||
|
else:
|
||||||
|
row.label(text="", icon='X')
|
||||||
|
|
||||||
|
def is_material_ready(self, material):
|
||||||
|
return bool(material.texture_atlas_albedo or
|
||||||
|
material.texture_atlas_normal or
|
||||||
|
material.texture_atlas_emission)
|
||||||
|
|
||||||
|
def calculate_atlas_size(self, context):
|
||||||
|
total_size = 0
|
||||||
|
for mat in context.scene.materials:
|
||||||
|
if mat.mat.include_in_atlas:
|
||||||
|
if mat.mat.texture_atlas_albedo:
|
||||||
|
img = bpy.data.images[mat.mat.texture_atlas_albedo]
|
||||||
|
total_size += img.size[0] * img.size[1]
|
||||||
|
return f"{int(sqrt(total_size))}x{int(sqrt(total_size))}"
|
||||||
|
|
||||||
@register_wrap
|
@register_wrap
|
||||||
class AvatarToolKit_PT_TextureAtlasPanel(Panel):
|
class AvatarToolKit_PT_TextureAtlasPanel(Panel):
|
||||||
@@ -106,4 +195,3 @@ class AvatarToolKit_PT_TextureAtlasPanel(Panel):
|
|||||||
icon='NODE_TEXTURE')
|
icon='NODE_TEXTURE')
|
||||||
else:
|
else:
|
||||||
layout.label(text=t("Tools.select_armature"), icon='ERROR')
|
layout.label(text=t("Tools.select_armature"), icon='ERROR')
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user