Merge pull request #151 from 989onan/patch-1
Many feature additions and improvements
This commit is contained in:
Vendored
+5
-5
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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'}
|
||||||
@@ -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'}
|
||||||
|
|||||||
@@ -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'}
|
||||||
|
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user