Improvements and fixes to some whoopsies

- Fixed double import
- Improved Cleanup mesh, includes removal of empty shapekeys and unused vertex groups.
- Optimise Armature now includes fix broken names function
- Optimise Weights now includes a limit for bone weights
- Material conversion now handles transparency
- automatically rename bones to a standard naming convention such as  _Left _Right

Still no where near complete, but I getting there.
This commit is contained in:
Yusarina
2024-09-26 00:11:52 +01:00
parent 6c918b8209
commit 3c2a63f29a
+81 -15
View File
@@ -1,6 +1,7 @@
import bpy import bpy
import numpy as np import numpy as np
from bpy.types import Operator, Context, Material, ShaderNodeTexImage, ShaderNodeGroup import re
from bpy.types import Operator, Context, Material, ShaderNodeTexImage, ShaderNodeGroup, Object
from ..core.register import register_wrap from ..core.register import register_wrap
from ..functions.translations import t from ..functions.translations import t
from ..core.common import get_selected_armature, is_valid_armature, get_all_meshes, init_progress, update_progress, finish_progress from ..core.common import get_selected_armature, is_valid_armature, get_all_meshes, init_progress, update_progress, finish_progress
@@ -26,24 +27,39 @@ class AvatarToolKit_OT_CleanupMesh(Operator):
update_progress(self, context, t("MMDOptions.removing_unused_vertex_groups")) update_progress(self, context, t("MMDOptions.removing_unused_vertex_groups"))
for obj in get_all_meshes(context): for obj in get_all_meshes(context):
self.remove_unused_vertex_groups(obj)
update_progress(self, context, t("MMDOptions.removing_unused_vertices"))
for obj in get_all_meshes(context):
self.remove_unused_vertices(obj)
update_progress(self, context, t("MMDOptions.removing_empty_shape_keys"))
for obj in get_all_meshes(context):
self.remove_empty_shape_keys(obj)
finish_progress(context)
return {'FINISHED'}
def remove_unused_vertex_groups(self, obj):
vgroups = obj.vertex_groups vgroups = obj.vertex_groups
for vgroup in vgroups: for vgroup in vgroups:
if not any(vgroup.index in [g.group for g in v.groups] for v in obj.data.vertices): if not any(vgroup.index in [g.group for g in v.groups] for v in obj.data.vertices):
vgroups.remove(vgroup) vgroups.remove(vgroup)
update_progress(self, context, t("MMDOptions.removing_unused_vertices")) def remove_unused_vertices(self, obj):
for obj in get_all_meshes(context):
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
obj.select_set(True) obj.select_set(True)
context.view_layer.objects.active = obj bpy.context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.delete_loose() bpy.ops.mesh.delete_loose()
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
update_progress(self, context, t("MMDOptions.cleanup_complete")) def remove_empty_shape_keys(self, obj):
finish_progress(context) if obj.data.shape_keys:
return {'FINISHED'} for key in obj.data.shape_keys.key_blocks:
if key.name != 'Basis' and all(abs(key.data[i].co[j] - obj.data.shape_keys.reference_key.data[i].co[j]) < 0.0001 for i in range(len(key.data)) for j in range(3)):
obj.shape_key_remove(key)
@register_wrap @register_wrap
class AvatarToolKit_OT_OptimizeWeights(Operator): class AvatarToolKit_OT_OptimizeWeights(Operator):
@@ -52,13 +68,21 @@ class AvatarToolKit_OT_OptimizeWeights(Operator):
bl_description = t("MMDOptions.optimize_weights.desc") bl_description = t("MMDOptions.optimize_weights.desc")
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
max_weights: bpy.props.IntProperty(
name=t("MMDOptions.max_weights.label"),
description=t("MMDOptions.max_weights.desc"),
default=4,
min=1,
max=8
)
def execute(self, context: Context) -> set[str]: def execute(self, context: Context) -> set[str]:
armature = get_selected_armature(context) armature = get_selected_armature(context)
if not armature: if not armature:
self.report({'ERROR'}, t("MMDOptions.no_armature_selected")) self.report({'ERROR'}, t("MMDOptions.no_armature_selected"))
return {'CANCELLED'} return {'CANCELLED'}
init_progress(context, 3) init_progress(context, 4)
update_progress(self, context, t("MMDOptions.merging_weights")) update_progress(self, context, t("MMDOptions.merging_weights"))
for obj in get_all_meshes(context): for obj in get_all_meshes(context):
@@ -69,10 +93,21 @@ class AvatarToolKit_OT_OptimizeWeights(Operator):
update_progress(self, context, t("MMDOptions.removing_zero_weight_bones")) update_progress(self, context, t("MMDOptions.removing_zero_weight_bones"))
bpy.ops.avatar_toolkit.remove_zero_weight_bones('EXEC_DEFAULT') bpy.ops.avatar_toolkit.remove_zero_weight_bones('EXEC_DEFAULT')
update_progress(self, context, t("MMDOptions.limiting_vertex_weights"))
for obj in get_all_meshes(context):
self.limit_vertex_weights(obj)
update_progress(self, context, t("MMDOptions.weight_optimization_complete")) update_progress(self, context, t("MMDOptions.weight_optimization_complete"))
finish_progress(context) finish_progress(context)
return {'FINISHED'} return {'FINISHED'}
def limit_vertex_weights(self, obj):
for v in obj.data.vertices:
if len(v.groups) > self.max_weights:
sorted_groups = sorted(v.groups, key=lambda g: g.weight, reverse=True)
for g in sorted_groups[self.max_weights:]:
obj.vertex_groups[g.group].remove([v.index])
@register_wrap @register_wrap
class AvatarToolKit_OT_OptimizeArmature(Operator): class AvatarToolKit_OT_OptimizeArmature(Operator):
bl_idname = "avatar_toolkit.optimize_armature" bl_idname = "avatar_toolkit.optimize_armature"
@@ -86,7 +121,7 @@ class AvatarToolKit_OT_OptimizeArmature(Operator):
self.report({'ERROR'}, t("MMDOptions.no_armature_selected")) self.report({'ERROR'}, t("MMDOptions.no_armature_selected"))
return {'CANCELLED'} return {'CANCELLED'}
init_progress(context, 7) init_progress(context, 9)
update_progress(self, context, t("MMDOptions.fixing_bone_rolls")) update_progress(self, context, t("MMDOptions.fixing_bone_rolls"))
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
@@ -111,6 +146,12 @@ class AvatarToolKit_OT_OptimizeArmature(Operator):
update_progress(self, context, t("MMDOptions.reordering_bones")) update_progress(self, context, t("MMDOptions.reordering_bones"))
self.reorder_bones(context, armature) self.reorder_bones(context, armature)
update_progress(self, context, t("MMDOptions.fixing_armature_names"))
self.fix_armature_names(armature)
update_progress(self, context, t("MMDOptions.renaming_bones"))
self.rename_bones(armature)
update_progress(self, context, t("MMDOptions.armature_optimization_complete")) update_progress(self, context, t("MMDOptions.armature_optimization_complete"))
finish_progress(context) finish_progress(context)
return {'FINISHED'} return {'FINISHED'}
@@ -126,12 +167,29 @@ class AvatarToolKit_OT_OptimizeArmature(Operator):
for root_bone in sorted(root_bones, key=lambda b: b.name): for root_bone in sorted(root_bones, key=lambda b: b.name):
sort_bones(root_bone) sort_bones(root_bone)
import bpy def fix_armature_names(self, armature):
import numpy as np for bone in armature.data.bones:
from bpy.types import Operator, Context, Material, ShaderNodeTexImage, ShaderNodeGroup, Object fixed_name = self.get_fixed_bone_name(bone.name)
from ..core.register import register_wrap if fixed_name != bone.name:
from ..functions.translations import t bone.name = fixed_name
from ..core.common import get_all_meshes, init_progress, update_progress, finish_progress
def get_fixed_bone_name(self, name):
name = name.replace(' ', '_')
name = re.sub(r'[^\w]', '', name)
return name
def rename_bones(self, armature):
for bone in armature.data.bones:
new_name = self.get_standardized_bone_name(bone.name)
if new_name != bone.name:
bone.name = new_name
def get_standardized_bone_name(self, name):
if 'left' in name.lower():
return f"Left_{name}"
elif 'right' in name.lower():
return f"Right_{name}"
return name
def bake_mmd_colors(node_base_tex: ShaderNodeTexImage, node_mmd_shader: ShaderNodeGroup): def bake_mmd_colors(node_base_tex: ShaderNodeTexImage, node_mmd_shader: ShaderNodeGroup):
ambient_color_input = node_mmd_shader.inputs.get("Ambient Color") ambient_color_input = node_mmd_shader.inputs.get("Ambient Color")
@@ -202,6 +260,7 @@ def add_principled_shader(material: Material, bake_mmd=True):
if node_base_tex and node_base_tex.image: if node_base_tex and node_base_tex.image:
links.new(node_base_tex.outputs["Color"], principled_shader.inputs["Base Color"]) links.new(node_base_tex.outputs["Color"], principled_shader.inputs["Base Color"])
links.new(node_base_tex.outputs["Alpha"], principled_shader.inputs["Alpha"])
elif principled_base_color is not None: elif principled_base_color is not None:
principled_shader.inputs["Base Color"].default_value = principled_base_color principled_shader.inputs["Base Color"].default_value = principled_base_color
@@ -211,6 +270,12 @@ def add_principled_shader(material: Material, bake_mmd=True):
principled_shader.inputs["Clearcoat Roughness"].default_value = 0 principled_shader.inputs["Clearcoat Roughness"].default_value = 0
principled_shader.inputs["IOR"].default_value = 1.45 principled_shader.inputs["IOR"].default_value = 1.45
# Handle transparency
if material.blend_method != 'OPAQUE':
principled_shader.inputs["Alpha"].default_value = material.alpha_threshold
material.blend_method = 'CLIP'
material.shadow_method = 'CLIP'
def fix_mmd_shader(material: Material): def fix_mmd_shader(material: Material):
mmd_shader_node = material.node_tree.nodes.get("mmd_shader") mmd_shader_node = material.node_tree.nodes.get("mmd_shader")
if mmd_shader_node: if mmd_shader_node:
@@ -301,3 +366,4 @@ class AvatarToolKit_OT_ConvertMaterials(Operator):
for link in input.links: for link in input.links:
if link.from_node not in used_nodes: if link.from_node not in used_nodes:
self.traverse_node_tree(link.from_node, used_nodes) self.traverse_node_tree(link.from_node, used_nodes)