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 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 ..functions.translations import t
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"))
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
for vgroup in vgroups:
if not any(vgroup.index in [g.group for g in v.groups] for v in obj.data.vertices):
vgroups.remove(vgroup)
update_progress(self, context, t("MMDOptions.removing_unused_vertices"))
for obj in get_all_meshes(context):
def remove_unused_vertices(self, obj):
bpy.ops.object.select_all(action='DESELECT')
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.mesh.select_all(action='SELECT')
bpy.ops.mesh.delete_loose()
bpy.ops.object.mode_set(mode='OBJECT')
update_progress(self, context, t("MMDOptions.cleanup_complete"))
finish_progress(context)
return {'FINISHED'}
def remove_empty_shape_keys(self, obj):
if obj.data.shape_keys:
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
class AvatarToolKit_OT_OptimizeWeights(Operator):
@@ -52,13 +68,21 @@ class AvatarToolKit_OT_OptimizeWeights(Operator):
bl_description = t("MMDOptions.optimize_weights.desc")
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]:
armature = get_selected_armature(context)
if not armature:
self.report({'ERROR'}, t("MMDOptions.no_armature_selected"))
return {'CANCELLED'}
init_progress(context, 3)
init_progress(context, 4)
update_progress(self, context, t("MMDOptions.merging_weights"))
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"))
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"))
finish_progress(context)
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
class AvatarToolKit_OT_OptimizeArmature(Operator):
bl_idname = "avatar_toolkit.optimize_armature"
@@ -86,7 +121,7 @@ class AvatarToolKit_OT_OptimizeArmature(Operator):
self.report({'ERROR'}, t("MMDOptions.no_armature_selected"))
return {'CANCELLED'}
init_progress(context, 7)
init_progress(context, 9)
update_progress(self, context, t("MMDOptions.fixing_bone_rolls"))
bpy.ops.object.mode_set(mode='EDIT')
@@ -111,6 +146,12 @@ class AvatarToolKit_OT_OptimizeArmature(Operator):
update_progress(self, context, t("MMDOptions.reordering_bones"))
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"))
finish_progress(context)
return {'FINISHED'}
@@ -126,12 +167,29 @@ class AvatarToolKit_OT_OptimizeArmature(Operator):
for root_bone in sorted(root_bones, key=lambda b: b.name):
sort_bones(root_bone)
import bpy
import numpy as np
from bpy.types import Operator, Context, Material, ShaderNodeTexImage, ShaderNodeGroup, Object
from ..core.register import register_wrap
from ..functions.translations import t
from ..core.common import get_all_meshes, init_progress, update_progress, finish_progress
def fix_armature_names(self, armature):
for bone in armature.data.bones:
fixed_name = self.get_fixed_bone_name(bone.name)
if fixed_name != bone.name:
bone.name = fixed_name
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):
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:
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:
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["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):
mmd_shader_node = material.node_tree.nodes.get("mmd_shader")
if mmd_shader_node:
@@ -301,3 +366,4 @@ class AvatarToolKit_OT_ConvertMaterials(Operator):
for link in input.links:
if link.from_node not in used_nodes:
self.traverse_node_tree(link.from_node, used_nodes)