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:
+81
-15
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user