Material Combiner Improvements

This commit is contained in:
Yusarina
2024-07-25 22:10:45 +01:00
parent 83366072b8
commit 61dbd7a79f
2 changed files with 32 additions and 38 deletions
+21 -28
View File
@@ -1,7 +1,7 @@
import bpy import bpy
import re import re
from typing import List, Tuple, Optional, Set from typing import List, Tuple, Optional, Set, Dict
from bpy.types import Material, Operator, Context, Object from bpy.types import Material, Operator, Context, Object, NodeTree
from ..core.common import clean_material_names, get_selected_armature, is_valid_armature, get_all_meshes from ..core.common import clean_material_names, get_selected_armature, is_valid_armature, get_all_meshes
from ..core.register import register_wrap from ..core.register import register_wrap
from ..functions.translations import t from ..functions.translations import t
@@ -21,31 +21,30 @@ def copy_tex_nodes(mat1: Material, mat2: Material) -> None:
node2.mapping = node1.mapping node2.mapping = node1.mapping
node2.projection = node1.projection node2.projection = node1.projection
def consolidate_textures(mat1: Material, mat2: Material) -> None: def consolidate_textures(node_tree1: NodeTree, node_tree2: NodeTree) -> None:
if mat1.node_tree and mat2.node_tree: for node1 in node_tree1.nodes:
for node1 in mat1.node_tree.nodes:
if node1.type == 'TEX_IMAGE': if node1.type == 'TEX_IMAGE':
if node1.node_tree: for node2 in node_tree2.nodes:
consolidate_textures(node1.node_tree, mat2.node_tree)
for node2 in mat2.node_tree.nodes:
if (node2.type == 'TEX_IMAGE' and if (node2.type == 'TEX_IMAGE' and
node1.image == node2.image): node1.image == node2.image):
consolidate_nodes(node1, node2) consolidate_nodes(node1, node2)
node2.image = node1.image node2.image = node1.image
copy_tex_nodes(mat1, mat2) elif node1.type == 'GROUP':
if node1.node_tree and node2.node_tree:
consolidate_textures(node1.node_tree, node2.node_tree)
def color_match(col1: Tuple[float, float, float, float], col2: Tuple[float, float, float, float], tolerance: float = 0.01) -> bool: def color_match(col1: Tuple[float, float, float, float], col2: Tuple[float, float, float, float], tolerance: float = 0.01) -> bool:
return abs(col1[0] - col2[0]) < tolerance return all(abs(c1 - c2) < tolerance for c1, c2 in zip(col1, col2))
def materials_match(mat1: Material, mat2: Material, tolerance: float = 0.01) -> bool: def materials_match(mat1: Material, mat2: Material, tolerance: float = 0.01) -> bool:
if not color_match(mat1.diffuse_color, mat2.diffuse_color, tolerance): if not color_match(mat1.diffuse_color, mat2.diffuse_color, tolerance):
return False return False
if mat1.roughness != mat2.roughness: if abs(mat1.roughness - mat2.roughness) > tolerance:
return False return False
consolidate_textures(mat1, mat2) if mat1.node_tree and mat2.node_tree:
consolidate_textures(mat1.node_tree, mat2.node_tree)
return True return True
@@ -53,9 +52,6 @@ def get_base_name(name: str) -> str:
mat_match = re.match(r"^(.*)\.\d{3}$", name) mat_match = re.match(r"^(.*)\.\d{3}$", name)
return mat_match.group(1) if mat_match else name return mat_match.group(1) if mat_match else name
def report_consolidated(self: Operator, num_combined: int) -> None:
self.report({'INFO'}, f"Combined {num_combined} materials")
@register_wrap @register_wrap
class CombineMaterials(Operator): class CombineMaterials(Operator):
bl_idname = "avatar_toolkit.combine_materials" bl_idname = "avatar_toolkit.combine_materials"
@@ -83,9 +79,9 @@ class CombineMaterials(Operator):
return {'CANCELLED'} return {'CANCELLED'}
self.consolidate_materials(meshes) self.consolidate_materials(meshes)
self.remove_unused_materials() self.clean_material_slots(meshes)
self.cleanmatslots()
self.clean_material_names() self.clean_material_names()
self.clear_unused_data_blocks()
return {'FINISHED'} return {'FINISHED'}
@@ -102,25 +98,19 @@ class CombineMaterials(Operator):
base_mat: Material = mat_mapping[base_name] base_mat: Material = mat_mapping[base_name]
try: try:
if materials_match(base_mat, mat): if materials_match(base_mat, mat):
consolidate_textures(base_mat, mat) consolidate_textures(base_mat.node_tree, mat.node_tree)
num_combined += 1 num_combined += 1
slot.material = base_mat slot.material = base_mat
except AttributeError: except AttributeError:
# Skip this material if there's an attribute mismatch self.report({'WARNING'}, t("Optimization.material_attribute_mismatch").format(material_name=mat.name))
continue continue
else: else:
mat_mapping[base_name] = mat mat_mapping[base_name] = mat
self.report({'INFO'}, t("Optimization.materials_combined").format(num_combined=num_combined)) self.report({'INFO'}, t("Optimization.materials_combined").format(num_combined=num_combined))
def remove_unused_materials(self) -> None: def clean_material_slots(self, meshes: List[Object]) -> None:
for mat in bpy.data.materials: for obj in meshes:
if not any(obj for obj in bpy.data.objects if obj.material_slots and mat.name in obj.material_slots):
bpy.data.materials.remove(mat, do_unlink=True)
def cleanmatslots(self) -> None:
for obj in bpy.data.objects:
if obj.type == 'MESH':
obj.select_set(True) obj.select_set(True)
bpy.context.view_layer.objects.active = obj bpy.context.view_layer.objects.active = obj
bpy.ops.object.material_slot_remove_unused() bpy.ops.object.material_slot_remove_unused()
@@ -130,3 +120,6 @@ class CombineMaterials(Operator):
for obj in bpy.data.objects: for obj in bpy.data.objects:
if obj.type == 'MESH': if obj.type == 'MESH':
clean_material_names(obj) clean_material_names(obj)
def clear_unused_data_blocks(self) -> None:
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
+1
View File
@@ -52,6 +52,7 @@
"Optimization.processing_mesh_no_shapekeys": "Processing mesh with no shapekeys named \"{mesh_name}\"", "Optimization.processing_mesh_no_shapekeys": "Processing mesh with no shapekeys named \"{mesh_name}\"",
"Optimization.remove_doubles_completed": "Remove doubles operation completed", "Optimization.remove_doubles_completed": "Remove doubles operation completed",
"Optimization.select_armature": "Please select an armature", "Optimization.select_armature": "Please select an armature",
"Optimization.material_attribute_mismatch": "Attribute mismatch in material {material_name}, skipping",
"Tools.select_armature": "Please select an armature", "Tools.select_armature": "Please select an armature",
"Tools.label": "Tools", "Tools.label": "Tools",
"Tools.tools_title.label": "Tools:", "Tools.tools_title.label": "Tools:",