Merge pull request #151 from 989onan/patch-1

Many feature additions and improvements
This commit is contained in:
Yusarina
2025-04-03 11:21:54 +01:00
committed by GitHub
20 changed files with 536 additions and 124 deletions
+5 -5
View File
@@ -1,11 +1,11 @@
{ {
"python.analysis.extraPaths": [ "python.analysis.extraPaths": [
"D:\\SteamLibrary\\steamapps\\common\\Blender\\4.3\\scripts\\addons", "D:\\SteamLibrary\\steamapps\\common\\Blender\\4.4\\scripts\\addons",
"C:\\Users\\Onan\\AppData\\Roaming\\Blender Foundation\\Blender\\4.3\\extensions\\user_default\\",//C:/Users/Onan/AppData/Roaming/Blender Foundation/Blender/4.0/scripts/addons "C:\\Users\\Onan\\AppData\\Roaming\\Blender Foundation\\Blender\\4.4\\extensions\\user_default\\",//C:/Users/Onan/AppData/Roaming/Blender Foundation/Blender/4.0/scripts/addons
"D:\\blender stuff\\blendercodestuff\\4.3", "D:\\blender stuff\\blendercodestuff\\4.3",
"D:\\SteamLibrary\\steamapps\\common\\Blender\\4.3\\python\\lib\\site-packages", "D:\\SteamLibrary\\steamapps\\common\\Blender\\4.4\\python\\lib\\site-packages",
"/Users/frankche/Documents/blendercoding/4.1/", "/Users/frankche/Documents/blendercoding/4.3/",
"/Users/frankche/Library/Application Support/Blender/4.3/extensions/user_default/" "/Users/frankche/Library/Application Support/Blender/4.4/extensions/user_default/"
], ],
"python.analysis.diagnosticSeverityOverrides": { "python.analysis.diagnosticSeverityOverrides": {
"reportInvalidTypeForm": "none" "reportInvalidTypeForm": "none"
+34 -5
View File
@@ -15,10 +15,9 @@ from bpy.types import (Context, Object, Modifier, EditBone, Operator, Material,
from functools import lru_cache from functools import lru_cache
from bpy.props import PointerProperty, IntProperty, StringProperty from bpy.props import PointerProperty, IntProperty, StringProperty
from bpy.utils import register_class from bpy.utils import register_class
from ..core.logging_setup import logger from .logging_setup import logger
from ..core.translations import t from .translations import t
from ..core.dictionaries import bone_names from .dictionaries import reverse_bone_lookup, simplify_bonename
from .dictionaries import reverse_bone_lookup, bone_names
class SceneMatClass(PropertyGroup): class SceneMatClass(PropertyGroup):
mat: PointerProperty(type=Material) mat: PointerProperty(type=Material)
@@ -383,7 +382,7 @@ def clear_unused_data_blocks() -> int:
if isinstance(getattr(bpy.data, attr), bpy.types.bpy_prop_collection)) if isinstance(getattr(bpy.data, attr), bpy.types.bpy_prop_collection))
return initial_count - final_count return initial_count - final_count
def identify_bones(arm_data: bpy.types.Armature, context: bpy.types.Context) -> Dict[str,str]: def identify_bones(arm_data: bpy.types.Armature) -> Dict[str,str]:
"""Identify bone names in an armature based on our reverse dictionary, so there is no confusion to what a bone is. """Identify bone names in an armature based on our reverse dictionary, so there is no confusion to what a bone is.
Essentially makes a dictionary of keys from dictionaries.bone_names like "hips", and the corosponding value is the bone that can be mapped to that key.""" Essentially makes a dictionary of keys from dictionaries.bone_names like "hips", and the corosponding value is the bone that can be mapped to that key."""
returned: Dict[str,str] = {} returned: Dict[str,str] = {}
@@ -495,6 +494,24 @@ def fix_zero_length_bones(armature: Object) -> None:
bone.length = 0.001 bone.length = 0.001
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
def remove_unused_vertex_groups(mesh: Object) -> int:
"""Remove vertex groups with no weights"""
removed: int = 0
for vg in mesh.vertex_groups:
has_weights: bool = False
for vert in mesh.data.vertices:
for group in vert.groups:
if group.group == vg.index and group.weight > 0.001:
has_weights = True
break
if has_weights:
break
if not has_weights:
mesh.vertex_groups.remove(vg)
removed = removed+1
return removed
def calculate_bone_orientation(mesh: Object, vertices: List[Any]) -> Tuple[Vector, float]: def calculate_bone_orientation(mesh: Object, vertices: List[Any]) -> Tuple[Vector, float]:
"""Calculate optimal bone orientation based on mesh geometry""" """Calculate optimal bone orientation based on mesh geometry"""
if not vertices: if not vertices:
@@ -518,6 +535,18 @@ def add_armature_modifier(mesh: Object, armature: Object) -> None:
modifier: Modifier = mesh.modifiers.new('Armature', 'ARMATURE') modifier: Modifier = mesh.modifiers.new('Armature', 'ARMATURE')
modifier.object = armature modifier.object = armature
def get_modifiers(self: Optional[Any] = None, context: Optional[Context] = None) -> List[Tuple[str, str, str]]:
returned: List[Tuple[str, str, str]] = []
if context.active_object == None:
return returned
if context.active_object.type != "MESH":
return returned
for mod in context.active_object.modifiers:
returned.append((mod.name,mod.name,""))
return returned
def get_shapekeys(context: Context, def get_shapekeys(context: Context,
names: List[str], names: List[str],
is_mouth: bool, is_mouth: bool,
-1
View File
@@ -3,7 +3,6 @@ from os import replace
from re import S from re import S
from types import FrameType from types import FrameType
import lz4.block
from . import resonite_types from . import resonite_types
from . import common from . import common
+3 -3
View File
@@ -5,10 +5,10 @@ from numpy import double
from typing import Set, Dict from typing import Set, Dict
import re import re
from .common import get_active_armature, simplify_bonename, validate_armature, ProgressTracker, identify_bones from .common import get_active_armature, ProgressTracker, identify_bones
from bpy.types import Context, Operator from bpy.types import Context, Operator
from ..core.translations import t from ..core.translations import t
from ..core.dictionaries import bone_names, resonite_translations from ..core.dictionaries import bone_names, resonite_translations, simplify_bonename
from ..core.logging_setup import logger from ..core.logging_setup import logger
from ..core.armature_validation import validate_armature from ..core.armature_validation import validate_armature
@@ -77,7 +77,7 @@ class AvatarToolkit_OT_ConvertResonite(Operator):
total_bones = len(arm_data.bones) total_bones = len(arm_data.bones)
with ProgressTracker(context, total_bones, t("Tools.convert_resonite.operation")) as progress: with ProgressTracker(context, total_bones, t("Tools.convert_resonite.operation")) as progress:
for key_simple,bone_name in identify_bones(arm_data,context).items(): for key_simple,bone_name in identify_bones(arm_data).items():
if key_simple in resonite_translations: if key_simple in resonite_translations:
new_name = resonite_translations[key_simple] new_name = resonite_translations[key_simple]
+14 -49
View File
@@ -2,15 +2,16 @@ import bpy
import numpy as np import numpy as np
from typing import List, Optional, Dict, Set, Tuple, Any from typing import List, Optional, Dict, Set, Tuple, Any
from bpy.types import Context, Object, Operator, ArmatureModifier, EditBone, VertexGroup, Mesh, ShapeKey from bpy.types import Context, Object, Operator, ArmatureModifier, EditBone, VertexGroup, Mesh, ShapeKey
from ...core.dictionaries import bone_names
from ...core.logging_setup import logger from ...core.logging_setup import logger
from ...core.translations import t from ...core.translations import t
from ...core.common import ( from ...core.common import (
get_all_meshes, get_all_meshes,
fix_zero_length_bones, fix_zero_length_bones,
remove_unused_vertex_groups,
clear_unused_data_blocks, clear_unused_data_blocks,
join_mesh_objects, join_mesh_objects,
remove_unused_shapekeys, remove_unused_shapekeys,
identify_bones,
) )
from ...core.dictionaries import simplify_bonename from ...core.dictionaries import simplify_bonename
@@ -175,44 +176,22 @@ def merge_armatures(
for bone in merge_armature_data.bones: for bone in merge_armature_data.bones:
original_parents[bone.name] = bone.parent.name if bone.parent else None original_parents[bone.name] = bone.parent.name if bone.parent else None
#create reverse lookup
reverse_bone_lookup = {}
for preferred_name, name_list in bone_names.items():
for name in name_list:
reverse_bone_lookup[name] = preferred_name
# Get base bone names
base_bone_names: Set[str] = {bone.name for bone in base_armature.data.bones}
base_armature_standards: Dict[str,Optional[str]] = {}
for bone in base_bone_names:
if simplify_bonename(bone) in reverse_bone_lookup:
base_armature_standards[reverse_bone_lookup[simplify_bonename(bone)]] = bone
# Switch to edit mode on merge armature and rename bones # Switch to edit mode on merge armature and rename bones
bpy.context.view_layer.objects.active = merge_armature bpy.context.view_layer.objects.active = merge_armature
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
# Handle bone renaming/removing to target armature. # Identify our bones to what their standard name is like "hips" for source and target armature bones.
bone_names_source: list[str] = [bone.name for bone in merge_armature_data.edit_bones] identifed_base_bone_names: Dict[str,str] = identify_bones(base_armature.data)
for bone in bone_names_source: identified_bone_names_source: Dict[str,str] = identify_bones(merge_armature_data)
bone_name = bone
if bone_name not in base_bone_names: #not auto mergable to original for standard,bone_name in identified_bone_names_source.items():
if standard in identifed_base_bone_names: #if the bone we are at on our merge armature has a standard name translation for the target armature
if simplify_bonename(bone_name) in reverse_bone_lookup: #if is a standard bone through standard translation. merge_armature_data.edit_bones[bone_name].name = identifed_base_bone_names[standard] #change it's name to the one on the target merge to armature's coorisponding standard bone
if reverse_bone_lookup[simplify_bonename(bone_name)] in base_armature_standards: #if this bone equals for example, "hips", does a bone that should be "hips" exist on our target armature? bone_name = merge_armature_data.edit_bones[bone_name].name
#if so, rename this bone to that one #adjust original parents list to point to the new name.
merge_armature_data.edit_bones[bone_name].name = base_armature_standards[reverse_bone_lookup[simplify_bonename(bone_name)]] for child_bone in merge_armature_data.edit_bones[bone_name]:
bone_name = merge_armature_data.edit_bones[bone_name].name original_parents[child_bone.name] = bone_name
#adjust original parents list to point to the new name. #then remove so it doesn't clash when merged.
for child_bone in merge_armature_data.edit_bones[bone_name]:
original_parents[child_bone.name] = bone_name
#then remove so it doesn't clash when merged.
merge_armature_data.edit_bones.remove(merge_armature_data.edit_bones[bone_name])
continue
#if it really doesn't have a counter part, just don't bother.
else:
merge_armature_data.edit_bones.remove(merge_armature_data.edit_bones[bone_name]) merge_armature_data.edit_bones.remove(merge_armature_data.edit_bones[bone_name])
# Return to object mode # Return to object mode
@@ -399,20 +378,6 @@ def mix_vertex_groups(mesh: Object, vg_from_name: str, vg_to_name: str) -> None:
vg_to.add(range(num_vertices), weights_combined.tolist(), 'REPLACE') vg_to.add(range(num_vertices), weights_combined.tolist(), 'REPLACE')
mesh.vertex_groups.remove(vg_from) mesh.vertex_groups.remove(vg_from)
def remove_unused_vertex_groups(mesh: Object) -> None:
"""Remove vertex groups with no weights"""
for vg in mesh.vertex_groups:
has_weights: bool = False
for vert in mesh.data.vertices:
for group in vert.groups:
if group.group == vg.index and group.weight > 0.001:
has_weights = True
break
if has_weights:
break
if not has_weights:
mesh.vertex_groups.remove(vg)
def apply_armature_to_mesh(armature: Object, mesh: Object) -> None: def apply_armature_to_mesh(armature: Object, mesh: Object) -> None:
"""Apply armature deformation to mesh""" """Apply armature deformation to mesh"""
armature_mod: ArmatureModifier = mesh.modifiers.new('PoseToRest', 'ARMATURE') armature_mod: ArmatureModifier = mesh.modifiers.new('PoseToRest', 'ARMATURE')
@@ -0,0 +1,139 @@
import traceback
import bpy
import re
from typing import Any, Set, Dict, List, Optional, Tuple
from bpy.types import (
Operator,
Context,
Object,
Material,
NodeTree,
ShaderNodeTexImage
)
import mathutils
import bmesh
from ...core.logging_setup import logger
from ...core.translations import t
from ...core.common import (
get_active_armature,
get_all_meshes,
ProgressTracker,
calculate_bone_orientation,
add_armature_modifier,
get_modifiers,
has_shapekeys
)
from ...core.armature_validation import validate_armature
class AvatarToolkit_OT_ApplyModifierForShapkeyObj(bpy.types.Operator):
"""Operator for forcing the application of a modifier. A shortened way of saying \"Apply modifier for object with shapekeys\""""
bl_idname: str = 'avatar_toolkit.merge_armatures'
bl_label: str = t('Tools.apply_modifier_on_shapekey_obj')
bl_description: str = t('Tools.apply_modifier_on_shapekey_obj_desc')
bl_options: Set[str] = {'REGISTER', 'UNDO'}
modifier: bpy.props.EnumProperty(items=get_modifiers,name="Modifier To Apply")
def draw(self, context: Context) -> None:
"""Draw the operator's UI"""
layout = self.layout
layout.prop(self, "modifier")
def invoke(self, context: Context, event: bpy.types.Event) -> set[str]:
"""Initialize the operator"""
return context.window_manager.invoke_props_dialog(self)
@classmethod
def poll(cls, context: Context) -> bool:
if context.active_object != None:
return context.active_object.type == "MESH"
return False
def execute(self, context: Context) -> Set[str]:
obj: bpy.types.Object = context.active_object
mesh: bpy.types.Mesh = obj.data
shapes: list[bpy.types.Object] = []
bpy.ops.object.mode_set(mode="OBJECT")
if has_shapekeys(obj):
#reset shapekeys
for idx,key in enumerate(mesh.shape_keys.key_blocks):
obj.active_shape_key_index = idx
obj.active_shape_key.value = 0
for idx,key in enumerate(mesh.shape_keys.key_blocks):
# duplicate object for shapekey
bpy.ops.object.select_all(action="DESELECT")
context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.duplicate()
# name new object after shapekey
new_obj = context.view_layer.objects.active
new_obj.select_set(True)
new_obj.active_shape_key_index = idx
new_obj.name = new_obj.active_shape_key.name
#add to cleanup list
shapes.append(new_obj)
#make basis the same shape as shapekey
for idx,point in enumerate(new_obj.active_shape_key.points):
new_obj.data.vertices[idx].co.xyz = point.co.xyz
#remove all shaoekeys on new object and then apply modifier
bpy.ops.object.shape_key_remove(all=True,apply_mix=False)
try:
bpy.ops.object.modifier_apply(modifier=self.modifier)
except Exception as e:
self.report({'ERROR'}, f"Shapekey modifier apply for shapekey \"{new_obj.name}\" failed!!")
print(f"Shapekey modifier apply for shapekey \"{new_obj.name}\" failed!!")
print(traceback.format_exc(e))
#clean up after critical failure
for shape in shapes:
bpy.data.objects.remove(shape)#faster than ops delete
bpy.ops.object.select_all(action="DESELECT")
try:
#remove shapekeys on original object
bpy.ops.object.select_all(action="DESELECT")
obj.select_set(True)
context.view_layer.objects.active = obj
bpy.ops.object.shape_key_remove(all=True,apply_mix=False)
bpy.ops.object.modifier_apply(modifier=self.modifier)
bpy.ops.object.select_all(action="DESELECT")
#delete first shapekey object aka basis
bpy.data.objects.remove(shapes.pop(0))
#join all objects with applied modifiers back together as shapes
for shape in shapes:
shape.select_set(True)
obj.select_set(True)
context.view_layer.objects.active = obj
bpy.ops.object.join_shapes()
except Exception as e:
self.report({'ERROR'}, f"Shapekey joining failed!!")
print(f"Shapekey joining failed!!")
print(traceback.format_exc(e))
#clean up after critical failure
for shape in shapes:
bpy.data.objects.remove(shape)#faster than ops delete
#final clean up
for shape in shapes:
bpy.data.objects.remove(shape)#faster than ops delete
else:
#mesh has no shapekeys, just apply normally.
bpy.ops.object.modifier_apply(modifier=self.modifier)
return {'FINISHED'}
+128 -2
View File
@@ -7,7 +7,9 @@ from ...core.common import (
get_active_armature, get_active_armature,
get_all_meshes, get_all_meshes,
ProgressTracker, ProgressTracker,
restore_bone_transforms restore_bone_transforms,
remove_unused_vertex_groups,
identify_bones,
) )
from ...core.armature_validation import validate_armature, validate_bone_hierarchy from ...core.armature_validation import validate_armature, validate_bone_hierarchy
@@ -262,6 +264,23 @@ class AvatarToolKit_OT_RemoveZeroWeightBones(Operator):
self.report({'INFO'}, t("Tools.clean_weights_success", count=removed_count)) self.report({'INFO'}, t("Tools.clean_weights_success", count=removed_count))
return {'FINISHED'} return {'FINISHED'}
class AvatarToolKit_OT_RemoveZeroWeightVertexGroups(Operator):
"""Operator to remove vertex groups with no weights"""
bl_idname = "avatar_toolkit.clean_vertex_groups"
bl_label = t("Tools.clean_vertex_groups")
bl_description = t("Tools.clean_vertex_groups_desc")
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context: Context) -> set[str]:
meshes: list[bpy.types.Object] = get_all_meshes(context)
removed: int = 0
for mesh_obj in meshes:
removed = removed+remove_unused_vertex_groups(mesh_obj)
self.report({'INFO'}, t("Tools.vertex_groups_removed", count=removed))
return {'FINISHED'}
class AvatarToolKit_OT_RemoveSelectedBones(Operator): class AvatarToolKit_OT_RemoveSelectedBones(Operator):
"""Operator to remove selected bones from the zero weight bones list""" """Operator to remove selected bones from the zero weight bones list"""
bl_idname = "avatar_toolkit.remove_selected_bones" bl_idname = "avatar_toolkit.remove_selected_bones"
@@ -285,4 +304,111 @@ class AvatarToolKit_OT_RemoveSelectedBones(Operator):
toolkit.zero_weight_bones.clear() toolkit.zero_weight_bones.clear()
self.report({'INFO'}, t("Tools.bones_removed", count=len(selected_bones))) self.report({'INFO'}, t("Tools.bones_removed", count=len(selected_bones)))
return {'FINISHED'} return {'FINISHED'}
class AvatarToolKit_OT_FlipCurrentKeyFrames(Operator):
"""Operator to flip the selected bone keyframes using blender's flip pose."""
bl_idname = "avatar_toolkit.flip_pose_frames"
bl_label = t("Tools.flip_pose_frames")
bl_description = t("Tools.flip_pose_frames_desc")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context: Context) -> bool:
"""Check if operator can be executed"""
armature = get_active_armature(context)
if not armature:
return False
if context.mode != 'POSE':
return False
if not armature.animation_data:
return False
valid, _, _ = validate_armature(armature)
return valid
def execute(self, context: Context) -> set[str]:
armature = get_active_armature(context)
armature_data: bpy.types.Armature = armature.data
standard_mappings: Dict[str,str] = identify_bones(armature_data)
# Do we need this? If flipping in the future has issues, then uncommenting this may help - @989onan
#To make sure our flip pose is extremely reliable, we're gonna temp rename all bones to standard names to make the posing work.
#for standard,bone_name in standard_mappings.items():
# armature_data.bones[bone_name].name = standard
#save our selection
selected: list[bool] = [False] * len(armature_data.bones)
armature_data.bones.foreach_get("select", selected)
#select everything
armature_data.bones.foreach_set("select", [False] * len(armature_data.bones))
#create a set for every frame time where we need to key a keyframe for the flipped pose
times: Dict[float,list[bpy.types.FCurve]] = {}
for curve in armature.animation_data.action.fcurves:
if not curve.data_path.startswith("pose"):
continue
for point in curve.keyframe_points:
if point.select_control_point:
if point.co.x not in times:
times[point.co.x] = []
times[point.co.x].append(curve)
for time,curves in times.items():
context.scene.frame_set(frame=int(time), subframe=float(time-float(int(time))))
armature_data.bones.foreach_set("select", [True] * len(armature_data.bones))
bpy.ops.pose.copy()
armature_data.bones.foreach_set("select", [False] * len(armature_data.bones))
bpy.ops.pose.paste(flipped=True,selected_mask=False)
for curve in curves:
bone_name: str = curve.data_path.replace("pose.bones[\"","")
bone_name = bone_name[:bone_name.index("\"")]
armature_data.bones[bone_name].select = True
bpy.ops.pose.select_mirror(extend=False)
#this can get the opposite side bone's data path and key it, if it is ever needed - @989onan
#for bone in armature_data.bones:
# if bone.select == True:
# bone_name = bone.name
# break
#new_path = curve.data_path[:curve.data_path.index("[")+1]+"\""+bone_name+"\""+curve.data_path[curve.data_path.index("]"):]
if armature.keyframe_insert(data_path=curve.data_path, index=curve.array_index, frame=time):
#if armature.keyframe_insert(data_path=new_path, index=curve.array_index, frame=time):
continue
self.report({'ERROR'}, f"Keyframe insertion for key with data path \"{curve.data_path}\" and frame {time} failed!")
return {'FINISHED'}
# Do we need this? If flipping in the future has issues, then uncommenting this may help - @989onan
#bring our names back as to not break their model.
#for standard,bone_name in standard_mappings.items():
# armature_data.bones[standard].name = bone_name
# restore selection
armature_data.bones.foreach_set("select", selected)
return {'FINISHED'}
+101
View File
@@ -0,0 +1,101 @@
import bpy
import numpy as np
from bpy.types import Operator, Context
from typing import Set
from ...core.translations import t
from ...core.logging_setup import logger
from ...core.common import get_active_armature, get_all_meshes
from ...core.armature_validation import validate_armature
import bmesh
class MapItem():
length: int
current_node: bmesh.types.BMVert
marched_paths: list[bmesh.types.BMEdge]
class AvatarToolkit_OT_SelectShortestSeamPath(Operator):
"""Find the shortest seam path between two vertices."""
bl_idname = "avatar_toolkit.find_shortest_seam_path"
bl_label = t("Tools.find_shortest_seam_path")
bl_description = t("Tools.find_shortest_seam_path_desc")
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context: Context) -> bool:
if context.mode != "EDIT_MESH":
return False
mesh_data: bpy.types.Mesh = context.active_object.data
mesh = bmesh.from_edit_mesh(mesh_data)
selected: int = 0
for vert in mesh.verts:
if vert.select == True:
selected = selected+1
if selected > 2:
return False
found_seam: bool = False
for edge in vert.link_edges:
if edge.seam:
found_seam = True
if not found_seam:
return False
if selected < 2:
return False
armature = get_active_armature(context)
if not armature:
return False
valid, _, _ = validate_armature(armature)
return valid
def execute(self, context: Context) -> Set[str]:
mesh_data: bpy.types.Mesh = context.active_object.data
mesh = bmesh.from_edit_mesh(mesh_data)
vert1: bmesh.types.BMVert = None
vert2: bmesh.types.BMVert = None
for vert in mesh.verts:
if vert.select == True:
if vert1 == None:
vert1 = vert
else:
vert2 = vert
current_verts: list[MapItem] = []
first_item: MapItem = MapItem()
first_item.current_node = vert1
first_item.length = 0
first_item.marched_paths = []
current_verts.append(first_item)
def find_next_edge() -> list[bmesh.types.BMEdge]:
if len(current_verts) == 0: #all paths have been exausted.
return []
for mapeditem in current_verts:
current_verts.remove(mapeditem)
for edge in mapeditem.current_node.link_edges:
if edge.seam and (edge not in mapeditem.marched_paths):
for vert_new in edge.verts:
if vert_new != mapeditem.current_node:
if vert_new == vert2:
mapeditem.marched_paths.append(edge)
return mapeditem.marched_paths
first_item: MapItem = MapItem()
first_item.current_node = vert_new
first_item.length = mapeditem.length+1
first_item.marched_paths = []
first_item.marched_paths.extend(mapeditem.marched_paths)
first_item.marched_paths.append(edge)
current_verts.append(first_item)
return find_next_edge()
mesh.select_flush(False)
path: list[bmesh.types.BMEdge] = find_next_edge()
for edge in path:
edge.select = True
for vert in edge.verts:
vert.select = True
bpy.ops.mesh.select_mode(type='EDGE')
return {'FINISHED'}
+2
View File
@@ -31,6 +31,8 @@ class AvatarToolkit_OT_AlignUVEdgesToTarget(Operator):
return False return False
if not context.space_data: if not context.space_data:
return False return False
if not hasattr(context.space_data, "show_uvedit"):
return False
if not context.space_data.show_uvedit: if not context.space_data.show_uvedit:
return False return False
if context.scene.tool_settings.use_uv_select_sync: if context.scene.tool_settings.use_uv_select_sync:
+2 -2
View File
@@ -124,7 +124,7 @@ class VisemePreview:
cls._preview_shapes = None cls._preview_shapes = None
cls._mesh_name = "" cls._mesh_name = ""
class ATOOLKIT_OT_preview_visemes(Operator): class AvatarToolkit_OT_PreviewVisemes(Operator):
"""Operator for previewing viseme shapes in real-time""" """Operator for previewing viseme shapes in real-time"""
bl_idname: str = "avatar_toolkit.preview_visemes" bl_idname: str = "avatar_toolkit.preview_visemes"
bl_label: str = t("Visemes.preview_label") bl_label: str = t("Visemes.preview_label")
@@ -181,7 +181,7 @@ def validate_deformation(mesh, mix_data):
mesh_size = max(mesh.dimensions) mesh_size = max(mesh.dimensions)
return max_deform < (mesh_size * 0.4) return max_deform < (mesh_size * 0.4)
class ATOOLKIT_OT_create_visemes(Operator): class AvatarToolkit_OT_CreateVisemes(Operator):
"""Operator for generating VRChat-compatible viseme shape keys""" """Operator for generating VRChat-compatible viseme shape keys"""
bl_idname: str = "avatar_toolkit.create_visemes" bl_idname: str = "avatar_toolkit.create_visemes"
bl_label: str = t("Visemes.create_label") bl_label: str = t("Visemes.create_label")
+10
View File
@@ -167,6 +167,7 @@
"Optimization.remove_doubles_completed": "Remove doubles completed successfully", "Optimization.remove_doubles_completed": "Remove doubles completed successfully",
"Tools.label": "Tools", "Tools.label": "Tools",
"Tools.mesh_title": "Mesh Tools",
"Tools.general_title": "General Tools", "Tools.general_title": "General Tools",
"Tools.select_armature": "Select an Armature", "Tools.select_armature": "Select an Armature",
"Tools.convert_resonite": "Convert to Resonite", "Tools.convert_resonite": "Convert to Resonite",
@@ -191,6 +192,8 @@
"Tools.merge_twist_bones_desc": "When checked, twist bones will be kept, even if there are zero-weight", "Tools.merge_twist_bones_desc": "When checked, twist bones will be kept, even if there are zero-weight",
"Tools.clean_weights": "Remove Zero Weight Bones", "Tools.clean_weights": "Remove Zero Weight Bones",
"Tools.clean_weights_desc": "Remove bones with no vertex weights", "Tools.clean_weights_desc": "Remove bones with no vertex weights",
"Tools.clean_vertex_groups": "Remove Unused Vertex Groups",
"Tools.clean_vertex_groups_desc": "Remove vertex groups on meshes assigned to no vertices.",
"Tools.preserve_parent_bones": "Preserve Parent Bones", "Tools.preserve_parent_bones": "Preserve Parent Bones",
"Tools.preserve_parent_bones_desc": "Keep bones that have children even if they have no weights", "Tools.preserve_parent_bones_desc": "Keep bones that have children even if they have no weights",
"Tools.target_bone_type": "Target Bone Type", "Tools.target_bone_type": "Target Bone Type",
@@ -203,7 +206,10 @@
"Tools.zero_weight_bones_found": "Zero weight bones found: {bones}", "Tools.zero_weight_bones_found": "Zero weight bones found: {bones}",
"Tools.remove_selected_bones": "Remove Selected Bones", "Tools.remove_selected_bones": "Remove Selected Bones",
"Tools.remove_selected_bones_desc": "Remove selected zero weight bones from armature", "Tools.remove_selected_bones_desc": "Remove selected zero weight bones from armature",
"Tools.flip_pose_frames": "Flip Selected Armature Key Frames",
"Tools.flip_pose_frames_desc": "Takes the selected keyframes and sets them to a mirrored pose, gotten from the opposite side of the armature on that frame.\nSelecting the entire animation's keyframes will flip the entire animation.",
"Tools.bones_removed": "Removed {count} bones", "Tools.bones_removed": "Removed {count} bones",
"Tools.vertex_groups_removed": "Removed {count} vertex groups.",
"Tools.clean_constraints": "Delete Bone Constraints", "Tools.clean_constraints": "Delete Bone Constraints",
"Tools.clean_constraints_desc": "Remove all bone constraints from armature", "Tools.clean_constraints_desc": "Remove all bone constraints from armature",
"Tools.clean_constraints_success": "Removed {count} bone constraints", "Tools.clean_constraints_success": "Removed {count} bone constraints",
@@ -211,6 +217,10 @@
"Tools.clean_weights_success": "Removed {count} zero-weight bones", "Tools.clean_weights_success": "Removed {count} zero-weight bones",
"Tools.clean_weights_threshold": "Weight Threshold", "Tools.clean_weights_threshold": "Weight Threshold",
"Tools.clean_weights_threshold_desc": "Minimum weight value to consider a bone as weighted", "Tools.clean_weights_threshold_desc": "Minimum weight value to consider a bone as weighted",
"Tools.find_shortest_seam_path": "Find Shortest Seam Path",
"Tools.find_shortest_seam_path_desc": "Find shortest path of seams between two selected vertices connected to seams.",
"Tools.apply_modifier_on_shapekey_obj":"Apply Modifier on Shapekey Object",
"Tools.apply_modifier_on_shapekey_obj_desc":"Applies a modifier on an object regardless of it having shapekeys.",
"Tools.merge_title": "Merge Tools", "Tools.merge_title": "Merge Tools",
"Tools.merge_to_active": "Merge to Active", "Tools.merge_to_active": "Merge to Active",
"Tools.merge_to_active_desc": "Merge selected bones to active bone", "Tools.merge_to_active_desc": "Merge selected bones to active bone",
+4 -4
View File
@@ -97,14 +97,14 @@ class AvatarToolKit_UL_MaterialTextureAtlasProperties(UIList):
row = layout.row(align=True) row = layout.row(align=True)
row.scale_y = 1.2 row.scale_y = 1.2
row.operator("avatar_toolkit.select_all_materials", text="", icon='CHECKBOX_HLT', row.operator(AvatarToolKit_OT_SelectAllMaterials.bl_idname, text="", icon='CHECKBOX_HLT',
emboss=True).tooltip = t("TextureAtlas.select_all_tooltip") emboss=True).tooltip = t("TextureAtlas.select_all_tooltip")
row.operator("avatar_toolkit.select_none_materials", text="", icon='CHECKBOX_DEHLT', row.operator(AvatarToolKit_OT_SelectNoneMaterials.bl_idname, text="", icon='CHECKBOX_DEHLT',
emboss=True).tooltip = t("TextureAtlas.select_none_tooltip") emboss=True).tooltip = t("TextureAtlas.select_none_tooltip")
row.separator(factor=0.5) row.separator(factor=0.5)
row.operator("avatar_toolkit.expand_all_materials", text="", icon='DISCLOSURE_TRI_DOWN', row.operator(AvatarToolKit_OT_ExpandAllMaterials.bl_idname, text="", icon='DISCLOSURE_TRI_DOWN',
emboss=True).tooltip = t("TextureAtlas.expand_all_tooltip") emboss=True).tooltip = t("TextureAtlas.expand_all_tooltip")
row.operator("avatar_toolkit.collapse_all_materials", text="", icon='DISCLOSURE_TRI_RIGHT', row.operator(AvatarToolKit_OT_CollapseAllMaterials.bl_idname, text="", icon='DISCLOSURE_TRI_RIGHT',
emboss=True).tooltip = t("TextureAtlas.collapse_all_tooltip") emboss=True).tooltip = t("TextureAtlas.collapse_all_tooltip")
row.separator(factor=1.0) row.separator(factor=1.0)
+7 -7
View File
@@ -175,12 +175,12 @@ class AvatarToolKit_PT_CustomPanel(Panel):
# Armature selection with better alignment # Armature selection with better alignment
row: UILayout = col.row(align=True) row: UILayout = col.row(align=True)
row.label(text=t('MergeArmature.into'), icon='ARMATURE_DATA') row.label(text=t('MergeArmature.into'), icon='ARMATURE_DATA')
row.operator("avatar_toolkit.search_merge_armature_into", row.operator(AvatarToolkit_OT_SearchMergeArmatureInto.bl_idname,
text=toolkit.merge_armature_into) text=toolkit.merge_armature_into)
row: UILayout = col.row(align=True) row: UILayout = col.row(align=True)
row.label(text=t('MergeArmature.from'), icon='ARMATURE_DATA') row.label(text=t('MergeArmature.from'), icon='ARMATURE_DATA')
row.operator("avatar_toolkit.search_merge_armature", row.operator(AvatarToolkit_OT_SearchMergeArmature.bl_idname,
text=toolkit.merge_armature) text=toolkit.merge_armature)
# Merge button with emphasis # Merge button with emphasis
@@ -188,7 +188,7 @@ class AvatarToolKit_PT_CustomPanel(Panel):
col: UILayout = merge_box.column(align=True) col: UILayout = merge_box.column(align=True)
row: UILayout = col.row(align=True) row: UILayout = col.row(align=True)
row.scale_y = 1.5 row.scale_y = 1.5
row.operator("avatar_toolkit.merge_armatures", icon='ARMATURE_DATA') row.operator(AvatarToolkit_OT_MergeArmature.bl_idname, icon='ARMATURE_DATA')
def draw_mesh_tools(self, layout: UILayout, context: Context) -> None: def draw_mesh_tools(self, layout: UILayout, context: Context) -> None:
"""Draw the mesh attachment tools section""" """Draw the mesh attachment tools section"""
@@ -213,17 +213,17 @@ class AvatarToolKit_PT_CustomPanel(Panel):
# Selection rows with icons and better alignment # Selection rows with icons and better alignment
row: UILayout = col.row(align=True) row: UILayout = col.row(align=True)
row.label(text=t('CustomPanel.select_armature'), icon='ARMATURE_DATA') row.label(text=t('CustomPanel.select_armature'), icon='ARMATURE_DATA')
row.operator("avatar_toolkit.search_merge_armature_into", row.operator(AvatarToolkit_OT_SearchMergeArmatureInto.bl_idname,
text=toolkit.merge_armature_into) text=toolkit.merge_armature_into)
row: UILayout = col.row(align=True) row: UILayout = col.row(align=True)
row.label(text=t('CustomPanel.select_mesh'), icon='MESH_DATA') row.label(text=t('CustomPanel.select_mesh'), icon='MESH_DATA')
row.operator("avatar_toolkit.search_attach_mesh", row.operator(AvatarToolkit_OT_SearchAttachMesh.bl_idname,
text=toolkit.attach_mesh) text=toolkit.attach_mesh)
row: UILayout = col.row(align=True) row: UILayout = col.row(align=True)
row.label(text=t('CustomPanel.select_bone'), icon='BONE_DATA') row.label(text=t('CustomPanel.select_bone'), icon='BONE_DATA')
row.operator("avatar_toolkit.search_attach_bone", row.operator(AvatarToolkit_OT_SearchAttachBone.bl_idname,
text=toolkit.attach_bone) text=toolkit.attach_bone)
# Attach button with emphasis # Attach button with emphasis
@@ -231,4 +231,4 @@ class AvatarToolKit_PT_CustomPanel(Panel):
col: UILayout = attach_box.column(align=True) col: UILayout = attach_box.column(align=True)
row: UILayout = col.row(align=True) row: UILayout = col.row(align=True)
row.scale_y = 1.5 row.scale_y = 1.5
row.operator("avatar_toolkit.attach_mesh", icon='ARMATURE_DATA') row.operator(AvatarToolkit_OT_AttachMesh.bl_idname, icon='ARMATURE_DATA')
+8 -5
View File
@@ -3,6 +3,9 @@ from typing import Set
from bpy.types import Panel, Context, UILayout, Operator from bpy.types import Panel, Context, UILayout, Operator
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
from ..core.translations import t from ..core.translations import t
from ..functions.optimization.materials_tools import AvatarToolkit_OT_CombineMaterials
from ..functions.optimization.remove_doubles import AvatarToolkit_OT_RemoveDoubles,AvatarToolkit_OT_RemoveDoublesAdvanced
from ..functions.optimization.mesh_tools import AvatarToolkit_OT_JoinAllMeshes, AvatarToolkit_OT_JoinSelectedMeshes
class AvatarToolKit_PT_OptimizationPanel(Panel): class AvatarToolKit_PT_OptimizationPanel(Panel):
"""Panel containing mesh and material optimization tools for avatar optimization""" """Panel containing mesh and material optimization tools for avatar optimization"""
@@ -26,7 +29,7 @@ class AvatarToolKit_PT_OptimizationPanel(Panel):
col.separator(factor=0.5) col.separator(factor=0.5)
# Material Operations # Material Operations
col.operator("avatar_toolkit.combine_materials", icon='MATERIAL') col.operator(AvatarToolkit_OT_CombineMaterials.bl_idname, icon='MATERIAL')
# Mesh Cleanup Box # Mesh Cleanup Box
cleanup_box: UILayout = layout.box() cleanup_box: UILayout = layout.box()
@@ -36,8 +39,8 @@ class AvatarToolKit_PT_OptimizationPanel(Panel):
# Remove Doubles Row # Remove Doubles Row
row: UILayout = col.row(align=True) row: UILayout = col.row(align=True)
row.operator("avatar_toolkit.remove_doubles", icon='MESH_DATA') row.operator(AvatarToolkit_OT_RemoveDoubles.bl_idname, icon='MESH_DATA')
row.operator("avatar_toolkit.remove_doubles_advanced", icon='PREFERENCES') row.operator(AvatarToolkit_OT_RemoveDoublesAdvanced.bl_idname, icon='PREFERENCES')
# Join Meshes Box # Join Meshes Box
join_box: UILayout = layout.box() join_box: UILayout = layout.box()
@@ -47,5 +50,5 @@ class AvatarToolKit_PT_OptimizationPanel(Panel):
# Join Meshes Row # Join Meshes Row
row: UILayout = col.row(align=True) row: UILayout = col.row(align=True)
row.operator("avatar_toolkit.join_all_meshes", icon='OBJECT_DATA') row.operator(AvatarToolkit_OT_JoinAllMeshes.bl_idname, icon='OBJECT_DATA')
row.operator("avatar_toolkit.join_selected_meshes", icon='RESTRICT_SELECT_OFF') row.operator(AvatarToolkit_OT_JoinSelectedMeshes.bl_idname, icon='RESTRICT_SELECT_OFF')
+10 -7
View File
@@ -23,7 +23,10 @@ from ..functions.pose_mode import (
AvatarToolkit_OT_ApplyPoseAsShapekey, AvatarToolkit_OT_ApplyPoseAsShapekey,
AvatarToolkit_OT_ApplyPoseAsRest AvatarToolkit_OT_ApplyPoseAsRest
) )
from ..core.armature_validation import validate_armature from ..core.armature_validation import validate_armature, AvatarToolkit_OT_ValidateTPose
from ..core.importers.importer import AvatarToolKit_OT_Import
from ..core.resonite_utils import AvatarToolKit_OT_ExportResonite
from ..functions.tools.standardize_armature import AvatarToolkit_OT_StandardizeArmature
class AvatarToolKit_OT_ExportFBX(Operator): class AvatarToolKit_OT_ExportFBX(Operator):
"""Export selected objects as FBX""" """Export selected objects as FBX"""
@@ -41,8 +44,8 @@ class AvatarToolKit_MT_ExportMenu(Menu):
def draw(self, context: Context) -> None: def draw(self, context: Context) -> None:
layout: UILayout = self.layout layout: UILayout = self.layout
layout.operator("avatar_toolkit.export_fbx", text=t("QuickAccess.export_fbx")) layout.operator(AvatarToolKit_OT_ExportFBX.bl_idname, text=t("QuickAccess.export_fbx"))
layout.operator("avatar_toolkit.export_resonite", text=t("QuickAccess.export_resonite")) layout.operator(AvatarToolKit_OT_ExportResonite.bl_idname, text=t("QuickAccess.export_resonite"))
class AvatarToolKit_OT_ExportMenu(Operator): class AvatarToolKit_OT_ExportMenu(Operator):
"""Open the export menu""" """Open the export menu"""
@@ -170,7 +173,7 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
col = pose_box.column(align=True) col = pose_box.column(align=True)
col.label(text=t("Validation.tpose.label"), icon='ARMATURE_DATA') col.label(text=t("Validation.tpose.label"), icon='ARMATURE_DATA')
col.separator(factor=0.5) col.separator(factor=0.5)
col.operator("avatar_toolkit.validate_tpose", icon='CHECKMARK') col.operator(AvatarToolkit_OT_ValidateTPose.bl_idname, icon='CHECKMARK')
if props.show_tpose_validation: if props.show_tpose_validation:
validation_box = col.box() validation_box = col.box()
@@ -207,7 +210,7 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
# Add standardize button # Add standardize button
standardize_box = info_box.box() standardize_box = info_box.box()
standardize_box.operator("avatar_toolkit.standardize_armature", standardize_box.operator(AvatarToolkit_OT_StandardizeArmature.bl_idname,
text=t("QuickAccess.standardize_armature"), text=t("QuickAccess.standardize_armature"),
icon='MODIFIER') icon='MODIFIER')
@@ -247,5 +250,5 @@ class AvatarToolKit_PT_QuickAccessPanel(Panel):
# Import/Export Buttons # Import/Export Buttons
button_row: UILayout = col.row(align=True) button_row: UILayout = col.row(align=True)
button_row.scale_y = 1.5 button_row.scale_y = 1.5
button_row.operator("avatar_toolkit.import", text=t("QuickAccess.import"), icon='IMPORT') button_row.operator(AvatarToolKit_OT_Import.bl_idname, text=t("QuickAccess.import"), icon='IMPORT')
button_row.operator("avatar_toolkit.export", text=t("QuickAccess.export"), icon='EXPORT') button_row.operator(AvatarToolKit_OT_ExportMenu.bl_idname, text=t("QuickAccess.export"), icon='EXPORT')
+3 -2
View File
@@ -10,6 +10,7 @@ from bpy.types import (
) )
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
from ..core.translations import t, get_languages_list from ..core.translations import t, get_languages_list
from ..core.armature_validation import AvatarToolkit_OT_HighlightProblemBones, AvatarToolkit_OT_ClearBoneHighlighting
class AvatarToolkit_OT_TranslationRestartPopup(Operator): class AvatarToolkit_OT_TranslationRestartPopup(Operator):
"""Popup dialog shown after language change to inform about restart requirement""" """Popup dialog shown after language change to inform about restart requirement"""
@@ -71,9 +72,9 @@ class AvatarToolKit_PT_SettingsPanel(Panel):
col.separator() col.separator()
col.prop(props, "highlight_problem_bones") col.prop(props, "highlight_problem_bones")
if props.highlight_problem_bones: if props.highlight_problem_bones:
col.operator("avatar_toolkit.highlight_problem_bones", icon='COLOR') col.operator(AvatarToolkit_OT_HighlightProblemBones.bl_idname, icon='COLOR')
else: else:
col.operator("avatar_toolkit.clear_bone_highlighting", icon='X') col.operator(AvatarToolkit_OT_ClearBoneHighlighting.bl_idname, icon='X')
# Debug Settings # Debug Settings
debug_box = layout.box() debug_box = layout.box()
+55 -25
View File
@@ -4,17 +4,22 @@ from bpy.types import Panel, Context, UILayout, Operator, UIList
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
from ..core.translations import t from ..core.translations import t
class AVATAR_TOOLKIT_UL_ZeroWeightBones(UIList): from ..core.resonite_utils import AvatarToolkit_OT_ConvertResonite
"""UI List for displaying zero weight bones with selection options""" from ..functions.tools.mesh_separation import AvatarToolKit_OT_SeparateByLooseParts, AvatarToolKit_OT_SeparateByMaterials
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): from ..functions.tools.additional_tools import AvatarToolkit_OT_ApplyTransforms, AvatarToolkit_OT_CleanShapekeys
if self.layout_type in {'DEFAULT', 'COMPACT'}: from ..functions.tools.bone_tools import (
row = layout.row(align=True) AvatarToolKit_OT_CreateDigitigradeLegs,
row.prop(item, "selected", text="") AvatarToolKit_OT_DeleteBoneConstraints,
row.label(text=item.name) AvatarToolKit_OT_RemoveSelectedBones,
if item.has_children: AvatarToolKit_OT_RemoveZeroWeightBones,
row.label(text="", icon='OUTLINER_OB_ARMATURE') AvatarToolKit_OT_RemoveZeroWeightVertexGroups,
if item.is_deform: AvatarToolKit_OT_FlipCurrentKeyFrames
row.label(text="", icon='MOD_ARMATURE') )
from ..functions.tools.standardize_armature import AvatarToolkit_OT_StandardizeArmature
from ..functions.tools.merge_tools import AvatarToolkit_OT_MergeToActive, AvatarToolkit_OT_MergeToParent, AvatarToolkit_OT_ConnectBones
from ..functions.tools.rigify_converter import AvatarToolkit_OT_ConvertRigifyToUnity
from ..functions.tools.general_mesh_tools import AvatarToolkit_OT_SelectShortestSeamPath
from ..functions.custom_tools.force_apply_modifier import AvatarToolkit_OT_ApplyModifierForShapkeyObj
class AvatarToolKit_PT_ToolsPanel(Panel): class AvatarToolKit_PT_ToolsPanel(Panel):
"""Panel containing various tools for avatar customization and optimization""" """Panel containing various tools for avatar customization and optimization"""
@@ -37,7 +42,7 @@ class AvatarToolKit_PT_ToolsPanel(Panel):
col: UILayout = tools_box.column(align=True) col: UILayout = tools_box.column(align=True)
col.label(text=t("Tools.general_title"), icon='TOOL_SETTINGS') col.label(text=t("Tools.general_title"), icon='TOOL_SETTINGS')
col.separator(factor=0.5) col.separator(factor=0.5)
col.operator("avatar_toolkit.convert_resonite", text=t("Tools.convert_resonite"), icon='EXPORT') col.operator(AvatarToolkit_OT_ConvertResonite.bl_idname, text=t("Tools.convert_resonite"), icon='EXPORT')
# Separation Tools # Separation Tools
sep_box: UILayout = layout.box() sep_box: UILayout = layout.box()
@@ -45,22 +50,32 @@ class AvatarToolKit_PT_ToolsPanel(Panel):
col.label(text=t("Tools.separate_title"), icon='MOD_EXPLODE') col.label(text=t("Tools.separate_title"), icon='MOD_EXPLODE')
col.separator(factor=0.5) col.separator(factor=0.5)
row: UILayout = col.row(align=True) row: UILayout = col.row(align=True)
row.operator("avatar_toolkit.separate_materials", text=t("Tools.separate_materials"), icon='MATERIAL') row.operator(AvatarToolKit_OT_SeparateByMaterials.bl_idname, text=t("Tools.separate_materials"), icon='MATERIAL')
row.operator("avatar_toolkit.separate_loose", text=t("Tools.separate_loose"), icon='MESH_DATA') row.operator(AvatarToolKit_OT_SeparateByLooseParts.bl_idname, text=t("Tools.separate_loose"), icon='MESH_DATA')
# Bone Tools # Bone Tools
bone_box: UILayout = layout.box() bone_box: UILayout = layout.box()
col = bone_box.column(align=True) col = bone_box.column(align=True)
col.label(text=t("Tools.bone_title"), icon='BONE_DATA') col.label(text=t("Tools.bone_title"), icon='BONE_DATA')
col.separator(factor=0.5) col.separator(factor=0.5)
col.operator("avatar_toolkit.create_digitigrade", text=t("Tools.create_digitigrade"), icon='BONE_DATA') col.operator(AvatarToolKit_OT_CreateDigitigradeLegs.bl_idname, text=t("Tools.create_digitigrade"), icon='BONE_DATA')
col.operator(AvatarToolKit_OT_FlipCurrentKeyFrames.bl_idname,text=t("Tools.flip_pose_frames"),icon="ACTION")
# Mesh Tools
mesh_box: UILayout = layout.box()
col = mesh_box.column(align=True)
col.label(text=t("Tools.mesh_title"), icon='MESH_DATA')
col.separator(factor=0.5)
col.operator(AvatarToolkit_OT_SelectShortestSeamPath.bl_idname,text=t("Tools.find_shortest_seam_path"),icon="MESH_DATA")
col.operator(AvatarToolkit_OT_ApplyModifierForShapkeyObj.bl_idname,text=t("Tools.apply_modifier_on_shapekey_obj"),icon="SHAPEKEY_DATA")
# Standardization Tools # Standardization Tools
standardize_box: UILayout = bone_box.box() standardize_box: UILayout = bone_box.box()
col = standardize_box.column(align=True) col = standardize_box.column(align=True)
col.label(text=t("Tools.standardize_title"), icon='OUTLINER_OB_ARMATURE') col.label(text=t("Tools.standardize_title"), icon='OUTLINER_OB_ARMATURE')
col.separator(factor=0.5) col.separator(factor=0.5)
col.operator("avatar_toolkit.standardize_armature", icon='CHECKMARK') col.operator(AvatarToolkit_OT_StandardizeArmature.bl_idname, icon='CHECKMARK')
# Weight Tools # Weight Tools
weight_box: UILayout = bone_box.box() weight_box: UILayout = bone_box.box()
@@ -78,12 +93,14 @@ class AvatarToolKit_PT_ToolsPanel(Panel):
toolkit, "zero_weight_bones_index") toolkit, "zero_weight_bones_index")
col = box.column(align=True) col = box.column(align=True)
col.operator("avatar_toolkit.remove_selected_bones", col.operator(AvatarToolKit_OT_RemoveSelectedBones.bl_idname,
text=t("Tools.remove_selected_bones")) text=t("Tools.remove_selected_bones"))
row = col.row(align=True) row = col.row(align=True)
row.operator("avatar_toolkit.clean_weights", text=t("Tools.clean_weights"), icon='GROUP_BONE') row.operator(AvatarToolKit_OT_RemoveZeroWeightBones.bl_idname, text=t("Tools.clean_weights"), icon='GROUP_BONE')
row.operator("avatar_toolkit.clean_constraints", text=t("Tools.clean_constraints"), icon='CONSTRAINT_BONE') row.operator(AvatarToolKit_OT_DeleteBoneConstraints.bl_idname, text=t("Tools.clean_constraints"), icon='CONSTRAINT_BONE')
row = col.row(align=True)
row.operator(AvatarToolKit_OT_RemoveZeroWeightVertexGroups.bl_idname, text=t("Tools.clean_vertex_groups"), icon='CONSTRAINT_BONE')
# Merge Tools # Merge Tools
merge_box: UILayout = layout.box() merge_box: UILayout = layout.box()
@@ -91,22 +108,35 @@ class AvatarToolKit_PT_ToolsPanel(Panel):
col.label(text=t("Tools.merge_title"), icon='AUTOMERGE_ON') col.label(text=t("Tools.merge_title"), icon='AUTOMERGE_ON')
col.separator(factor=0.5) col.separator(factor=0.5)
row = col.row(align=True) row = col.row(align=True)
row.operator("avatar_toolkit.merge_to_active", text=t("Tools.merge_to_active"), icon='BONE_DATA') row.operator(AvatarToolkit_OT_MergeToActive.bl_idname, text=t("Tools.merge_to_active"), icon='BONE_DATA')
row.operator("avatar_toolkit.merge_to_parent", text=t("Tools.merge_to_parent"), icon='BONE_DATA') row.operator(AvatarToolkit_OT_MergeToParent.bl_idname, text=t("Tools.merge_to_parent"), icon='BONE_DATA')
col.operator("avatar_toolkit.connect_bones", text=t("Tools.connect_bones"), icon='BONE_DATA') col.operator(AvatarToolkit_OT_ConnectBones.bl_idname, text=t("Tools.connect_bones"), icon='BONE_DATA')
# Additional Tools # Additional Tools
extra_box: UILayout = layout.box() extra_box: UILayout = layout.box()
col = extra_box.column(align=True) col = extra_box.column(align=True)
col.label(text=t("Tools.additional_title"), icon='TOOL_SETTINGS') col.label(text=t("Tools.additional_title"), icon='TOOL_SETTINGS')
col.separator(factor=0.5) col.separator(factor=0.5)
col.operator("avatar_toolkit.apply_transforms", text=t("Tools.apply_transforms"), icon='OBJECT_DATA') col.operator(AvatarToolkit_OT_ApplyTransforms.bl_idname, text=t("Tools.apply_transforms"), icon='OBJECT_DATA')
col.operator("avatar_toolkit.clean_shapekeys", text=t("Tools.clean_shapekeys"), icon='SHAPEKEY_DATA') col.operator(AvatarToolkit_OT_CleanShapekeys.bl_idname, text=t("Tools.clean_shapekeys"), icon='SHAPEKEY_DATA')
# Rigify Tools # Rigify Tools
rigify_box: UILayout = layout.box() rigify_box: UILayout = layout.box()
col = rigify_box.column(align=True) col = rigify_box.column(align=True)
col.label(text=t("Tools.rigify_title"), icon='ARMATURE_DATA') col.label(text=t("Tools.rigify_title"), icon='ARMATURE_DATA')
col.separator(factor=0.5) col.separator(factor=0.5)
col.operator("avatar_toolkit.convert_rigify_to_unity", icon='ARMATURE_DATA') col.operator(AvatarToolkit_OT_ConvertRigifyToUnity.bl_idname, icon='ARMATURE_DATA')
col.prop(context.scene.avatar_toolkit, "merge_twist_bones") col.prop(context.scene.avatar_toolkit, "merge_twist_bones")
class AVATAR_TOOLKIT_UL_ZeroWeightBones(UIList):
"""UI List for displaying zero weight bones with selection options"""
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if self.layout_type in {'DEFAULT', 'COMPACT'}:
row = layout.row(align=True)
row.prop(item, "selected", text="")
row.label(text=item.name)
if item.has_children:
row.label(text="", icon='OUTLINER_OB_ARMATURE')
if item.is_deform:
row.label(text="", icon='MOD_ARMATURE')
+2 -1
View File
@@ -1,6 +1,7 @@
import bpy import bpy
from bpy.types import Panel, Context, UILayout from bpy.types import Panel, Context, UILayout
from ..core.translations import t from ..core.translations import t
from .main_panel import CATEGORY_NAME
class AvatarToolKit_PT_UVPanel(Panel): class AvatarToolKit_PT_UVPanel(Panel):
"""Main UV Tools panel for Avatar Toolkit""" """Main UV Tools panel for Avatar Toolkit"""
@@ -8,7 +9,7 @@ class AvatarToolKit_PT_UVPanel(Panel):
bl_idname = "OBJECT_PT_avatar_toolkit_uv_main" bl_idname = "OBJECT_PT_avatar_toolkit_uv_main"
bl_space_type = 'IMAGE_EDITOR' bl_space_type = 'IMAGE_EDITOR'
bl_region_type = 'UI' bl_region_type = 'UI'
bl_category = "Avatar Toolkit" bl_category = CATEGORY_NAME
def draw(self, context: Context) -> None: def draw(self, context: Context) -> None:
layout: UILayout = self.layout layout: UILayout = self.layout
+6 -4
View File
@@ -1,6 +1,8 @@
import bpy import bpy
from bpy.types import Panel, Context, UILayout from bpy.types import Panel, Context, UILayout
from ..core.translations import t from ..core.translations import t
from ..functions.tools.uv_tools import AvatarToolkit_OT_AlignUVEdgesToTarget
from .uv_panel import AvatarToolKit_PT_UVPanel
class AvatarToolKit_PT_UVTools(Panel): class AvatarToolKit_PT_UVTools(Panel):
"""UV Tools panel containing UV manipulation operators""" """UV Tools panel containing UV manipulation operators"""
@@ -8,9 +10,9 @@ class AvatarToolKit_PT_UVTools(Panel):
bl_idname = "OBJECT_PT_avatar_toolkit_uv_tools" bl_idname = "OBJECT_PT_avatar_toolkit_uv_tools"
bl_space_type = 'IMAGE_EDITOR' bl_space_type = 'IMAGE_EDITOR'
bl_region_type = 'UI' bl_region_type = 'UI'
bl_category = "Avatar Toolkit" bl_category = "UV Tools"
bl_parent_id = "OBJECT_PT_avatar_toolkit_uv_main" bl_parent_id = AvatarToolKit_PT_UVPanel.bl_idname
bl_order = 3 bl_order = 0
bl_options = {'DEFAULT_CLOSED'} bl_options = {'DEFAULT_CLOSED'}
def draw(self, context: Context) -> None: def draw(self, context: Context) -> None:
@@ -22,6 +24,6 @@ class AvatarToolKit_PT_UVTools(Panel):
col.separator(factor=0.5) col.separator(factor=0.5)
row: UILayout = col.row(align=True) row: UILayout = col.row(align=True)
row.operator("avatar_toolkit.align_uv_edges_to_target", row.operator(AvatarToolkit_OT_AlignUVEdgesToTarget.bl_idname,
text=t("UVTools.align_edges"), text=t("UVTools.align_edges"),
icon='GP_MULTIFRAME_EDITING') icon='GP_MULTIFRAME_EDITING')
+3 -2
View File
@@ -3,6 +3,7 @@ from bpy.types import Panel, Context, UILayout, Object, ShapeKey
from ..core.translations import t from ..core.translations import t
from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from .main_panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
from ..core.common import get_active_armature from ..core.common import get_active_armature
from ..functions.visemes import AvatarToolkit_OT_PreviewVisemes, AvatarToolkit_OT_CreateVisemes
class AvatarToolKit_PT_VisemesPanel(Panel): class AvatarToolKit_PT_VisemesPanel(Panel):
"""Panel containing viseme creation and preview tools""" """Panel containing viseme creation and preview tools"""
@@ -65,11 +66,11 @@ class AvatarToolKit_PT_VisemesPanel(Panel):
col.separator() col.separator()
preview_text: str = t("Visemes.stop_preview") if props.viseme_preview_mode else t("Visemes.start_preview") preview_text: str = t("Visemes.stop_preview") if props.viseme_preview_mode else t("Visemes.start_preview")
col.operator("avatar_toolkit.preview_visemes", text=preview_text, icon='HIDE_OFF') col.operator(AvatarToolkit_OT_PreviewVisemes.bl_idname, text=preview_text, icon='HIDE_OFF')
# Create Box # Create Box
create_box: UILayout = layout.box() create_box: UILayout = layout.box()
col: UILayout = create_box.column(align=True) col: UILayout = create_box.column(align=True)
col.label(text=t("Visemes.create_label"), icon='ADD') col.label(text=t("Visemes.create_label"), icon='ADD')
col.separator(factor=0.5) col.separator(factor=0.5)
col.operator("avatar_toolkit.create_visemes", icon='ADD') col.operator(AvatarToolkit_OT_CreateVisemes.bl_idname, icon='ADD')