Basic Start of Armature Selection

This commit is contained in:
Yusarina
2024-07-22 23:13:10 +01:00
parent f0323577c9
commit 06c7cff4b7
9 changed files with 86 additions and 74 deletions
+12
View File
@@ -57,3 +57,15 @@ def get_armature(context, armature_name=None) -> Optional[Object]:
if obj.type == "ARMATURE": if obj.type == "ARMATURE":
return obj return obj
return next((obj for obj in context.view_layer.objects if obj.type == 'ARMATURE'), None) return next((obj for obj in context.view_layer.objects if obj.type == 'ARMATURE'), None)
def get_armatures(self, context):
return [(obj.name, obj.name, "") for obj in bpy.data.objects if obj.type == 'ARMATURE']
def get_selected_armature(context):
if context.scene.selected_armature:
return bpy.data.objects.get(context.scene.selected_armature)
return None
def set_selected_armature(context, armature):
context.scene.selected_armature = armature.name if armature else ""
+10
View File
@@ -1,6 +1,7 @@
import bpy import bpy
from ..functions.translations import t, get_languages_list, update_language from ..functions.translations import t, get_languages_list, update_language
from ..core.addon_preferences import get_preference from ..core.addon_preferences import get_preference
from .common import get_armatures
def register(): def register():
default_language = get_preference("language", 0) default_language = get_preference("language", 0)
@@ -15,9 +16,18 @@ def register():
bpy.types.Scene.avatar_toolkit_language_changed = bpy.props.BoolProperty(default=False) bpy.types.Scene.avatar_toolkit_language_changed = bpy.props.BoolProperty(default=False)
bpy.types.Scene.selected_armature = bpy.props.EnumProperty(
items=get_armatures,
name="Selected Armature",
description="The currently selected armature for Avatar Toolkit operations"
)
def unregister(): def unregister():
if hasattr(bpy.types.Scene, "avatar_toolkit_language"): if hasattr(bpy.types.Scene, "avatar_toolkit_language"):
del bpy.types.Scene.avatar_toolkit_language del bpy.types.Scene.avatar_toolkit_language
if hasattr(bpy.types.Scene, "avatar_toolkit_language_changed"): if hasattr(bpy.types.Scene, "avatar_toolkit_language_changed"):
del bpy.types.Scene.avatar_toolkit_language_changed del bpy.types.Scene.avatar_toolkit_language_changed
if hasattr(bpy.types.Scene, "selected_armature"):
del bpy.types.Scene.selected_armature
+5 -4
View File
@@ -2,7 +2,7 @@ import bpy
import re import re
from typing import List, Tuple, Optional from typing import List, Tuple, Optional
from bpy.types import Material, Operator, Context, Object from bpy.types import Material, Operator, Context, Object
from ..core.common import clean_material_names from ..core.common import clean_material_names, get_selected_armature
from ..core.register import register_wrap from ..core.register import register_wrap
from ..functions.translations import t from ..functions.translations import t
@@ -65,17 +65,19 @@ class CombineMaterials(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return context.active_object is not None return context.active_object is not None and get_selected_armature(context) is not None
def execute(self, context: Context) -> set: def execute(self, context: Context) -> set:
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
armature: Optional[Object] = next((obj for obj in bpy.data.objects if obj.type == 'ARMATURE'), None) armature = get_selected_armature(context)
if not armature: if not armature:
self.report({'WARNING'}, "No armature selected")
return {'CANCELLED'} return {'CANCELLED'}
meshes: List[Object] = [obj for obj in bpy.data.objects if obj.type == 'MESH' and 'Armature' in obj.modifiers and obj.modifiers['Armature'].object == armature] meshes: List[Object] = [obj for obj in bpy.data.objects if obj.type == 'MESH' and 'Armature' in obj.modifiers and obj.modifiers['Armature'].object == armature]
if not meshes: if not meshes:
self.report({'WARNING'}, "No meshes found for the selected armature")
return {'CANCELLED'} return {'CANCELLED'}
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
@@ -125,4 +127,3 @@ class CombineMaterials(Operator):
for obj in bpy.data.objects: for obj in bpy.data.objects:
if obj.type == 'MESH': if obj.type == 'MESH':
clean_material_names(obj) clean_material_names(obj)
+9 -9
View File
@@ -2,7 +2,7 @@ import bpy
from typing import List, Optional from typing import List, Optional
from bpy.types import Operator, Context, Object from bpy.types import Operator, Context, Object
from ..core.register import register_wrap from ..core.register import register_wrap
from ..core.common import fix_uv_coordinates from ..core.common import fix_uv_coordinates, get_selected_armature
from ..functions.translations import t from ..functions.translations import t
@register_wrap @register_wrap
@@ -14,21 +14,22 @@ class JoinAllMeshes(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return context.mode == 'OBJECT' return context.mode == 'OBJECT' and get_selected_armature(context) is not None
def execute(self, context: Context) -> set: def execute(self, context: Context) -> set:
self.join_all_meshes(context) self.join_all_meshes(context)
return {'FINISHED'} return {'FINISHED'}
def join_all_meshes(self, context: Context) -> None: def join_all_meshes(self, context: Context) -> None:
if not bpy.data.objects: armature = get_selected_armature(context)
self.report({'INFO'}, "No objects in the scene") if not armature:
self.report({'WARNING'}, "No armature selected")
return return
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
meshes: List[Object] = [obj for obj in bpy.data.objects if obj.type == 'MESH'] meshes: List[Object] = [obj for obj in bpy.data.objects if obj.type == 'MESH' and 'Armature' in obj.modifiers and obj.modifiers['Armature'].object == armature]
for mesh in meshes: for mesh in meshes:
mesh.select_set(True) mesh.select_set(True)
@@ -52,7 +53,7 @@ class JoinSelectedMeshes(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return context.mode == 'OBJECT' return context.mode == 'OBJECT' and len([obj for obj in context.selected_objects if obj.type == 'MESH']) > 1
def execute(self, context: Context) -> set: def execute(self, context: Context) -> set:
self.join_selected_meshes(context) self.join_selected_meshes(context)
@@ -61,8 +62,8 @@ class JoinSelectedMeshes(Operator):
def join_selected_meshes(self, context: Context) -> None: def join_selected_meshes(self, context: Context) -> None:
selected_objects: List[Object] = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH'] selected_objects: List[Object] = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH']
if not selected_objects: if len(selected_objects) < 2:
self.report({'WARNING'}, "No mesh objects selected") self.report({'WARNING'}, "Please select at least two mesh objects")
return return
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
@@ -81,4 +82,3 @@ class JoinSelectedMeshes(Operator):
self.report({'INFO'}, "Selected meshes joined successfully") self.report({'INFO'}, "Selected meshes joined successfully")
else: else:
self.report({'WARNING'}, "No mesh objects selected") self.report({'WARNING'}, "No mesh objects selected")
+8 -27
View File
@@ -5,7 +5,7 @@ import re
from typing import List, Tuple, Optional, TypedDict from typing import List, Tuple, Optional, TypedDict
from bpy.types import Material, Operator, Context, Object from bpy.types import Material, Operator, Context, Object
from ..core.register import register_wrap from ..core.register import register_wrap
from ..core.common import get_armature from ..core.common import get_selected_armature
class meshEntry(TypedDict): class meshEntry(TypedDict):
@@ -23,20 +23,19 @@ class RemoveDoublesSafely(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return context.mode == 'OBJECT' return context.mode == 'OBJECT' and get_selected_armature(context) is not None
def execute(self, context: Context) -> set: def execute(self, context: Context) -> set:
if not bpy.data.objects: armature = get_selected_armature(context)
self.report({'INFO'}, "No objects in the scene") if not armature:
return self.report({'WARNING'}, "No armature selected")
return {'CANCELLED'}
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
objects: List[Object] = get_armature(context).children if get_armature(context) else context.view_layer.objects objects: List[Object] = [obj for obj in armature.children if obj.type == 'MESH']
meshes: List[Object] = [obj for obj in objects if obj.type == 'MESH'] for mesh in objects:
for mesh in meshes:
if mesh.data.name not in [stored_object["mesh"].data.name for stored_object in self.objects_to_do]: if mesh.data.name not in [stored_object["mesh"].data.name for stored_object in self.objects_to_do]:
mesh_shapekeys = {"mesh":mesh,"shapekeys":[]} mesh_shapekeys = {"mesh":mesh,"shapekeys":[]}
mesh_data: bpy.types.Mesh = mesh.data mesh_data: bpy.types.Mesh = mesh.data
@@ -46,11 +45,9 @@ class RemoveDoublesSafely(Operator):
mesh_shapekeys["shapekeys"].append(shape.name) mesh_shapekeys["shapekeys"].append(shape.name)
self.objects_to_do.append(mesh_shapekeys) self.objects_to_do.append(mesh_shapekeys)
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context: Context, event: bpy.types.Event) -> set: def invoke(self, context: Context, event: bpy.types.Event) -> set:
self.execute(context) self.execute(context)
context.window_manager.modal_handler_add(self) context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
@@ -62,7 +59,6 @@ class RemoveDoublesSafely(Operator):
mesh_data: bpy.types.Mesh = mesh["mesh"].data mesh_data: bpy.types.Mesh = mesh["mesh"].data
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
for index, point in enumerate(mesh["mesh"].active_shape_key.points): for index, point in enumerate(mesh["mesh"].active_shape_key.points):
if point.co.xyz != mesh_data.shape_keys.key_blocks[0].points[index].co.xyz: if point.co.xyz != mesh_data.shape_keys.key_blocks[0].points[index].co.xyz:
@@ -70,18 +66,10 @@ class RemoveDoublesSafely(Operator):
print("shapekey has a moved vertex at index \""+str(index)+"\", excluding from double merging!") print("shapekey has a moved vertex at index \""+str(index)+"\", excluding from double merging!")
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.mode_set(mode='OBJECT')
mesh["mesh"].select_set(False) mesh["mesh"].select_set(False)
def modal(self, context: Context, event: bpy.types.Event) -> set: def modal(self, context: Context, event: bpy.types.Event) -> set:
if len(self.objects_to_do) > 0: if len(self.objects_to_do) > 0:
mesh = self.objects_to_do[0] mesh = self.objects_to_do[0]
mesh_data: bpy.types.Mesh = mesh["mesh"].data mesh_data: bpy.types.Mesh = mesh["mesh"].data
@@ -131,10 +119,3 @@ class RemoveDoublesSafely(Operator):
return {'FINISHED'} return {'FINISHED'}
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
+6 -8
View File
@@ -4,7 +4,7 @@ from typing import List, Optional
import re import re
from bpy.types import Operator, Context, Object from bpy.types import Operator, Context, Object
from ..core.dictionaries import bone_names from ..core.dictionaries import bone_names
from ..core.common import get_armature, simplify_bonename from ..core.common import get_selected_armature, simplify_bonename
from ..functions.translations import t from ..functions.translations import t
@register_wrap @register_wrap
@@ -16,12 +16,13 @@ class ConvertToResonite(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
if not get_armature(context): return get_selected_armature(context) is not None
return False
return True
def execute(self, context: Context) -> set: def execute(self, context: Context) -> set:
armature = get_armature(context) armature = get_selected_armature(context)
if not armature:
self.report({'WARNING'}, "No armature selected")
return {'CANCELLED'}
translate_bone_fails = 0 translate_bone_fails = 0
untranslated_bones = set() untranslated_bones = set()
@@ -89,8 +90,6 @@ class ConvertToResonite(Operator):
'thumb_3_r': "thumb3.R" 'thumb_3_r': "thumb3.R"
} }
context.view_layer.objects.active = armature context.view_layer.objects.active = armature
bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='EDIT')
@@ -111,5 +110,4 @@ class ConvertToResonite(Operator):
else: else:
self.report({'INFO'}, "Successfully translated all bones to humanoid names") self.report({'INFO'}, "Successfully translated all bones to humanoid names")
return {'FINISHED'} return {'FINISHED'}
+15 -13
View File
@@ -2,6 +2,7 @@ import bpy
from ..core.register import register_wrap from ..core.register import register_wrap
from .panel import AvatarToolkitPanel from .panel import AvatarToolkitPanel
from ..functions.translations import t from ..functions.translations import t
from ..core.common import get_selected_armature
@register_wrap @register_wrap
class AvatarToolkitOptimizationPanel(bpy.types.Panel): class AvatarToolkitOptimizationPanel(bpy.types.Panel):
@@ -14,20 +15,21 @@ class AvatarToolkitOptimizationPanel(bpy.types.Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.label(text=t("Optimization.options.label")) armature = get_selected_armature(context)
row = layout.row() if armature:
row.scale_y = 1.2 layout.label(text=t("Optimization.options.label"))
row.operator("avatar_toolkit.combine_materials", text=t("Optimization.combine_materials.label"))
row = layout.row()
row.scale_y = 1.2
row.operator("avatar_toolkit.combine_materials", text=t("Optimization.combine_materials.label"))
layout.separator(factor=0.5) layout.separator(factor=0.5)
row = layout.row(align=True)
row.scale_y = 1.2
row.operator("avatar_toolkit.join_all_meshes", text=t("Optimization.join_all_meshes.label"))
row.operator("avatar_toolkit.join_selected_meshes", text=t("Optimization.join_selected_meshes.label"))
row.operator("avatar_toolkit.remove_doubles_safely", text="Remove Doubles Safely")
# Add optimization options here
row = layout.row(align=True)
row.scale_y = 1.2
row.operator("avatar_toolkit.join_all_meshes", text=t("Optimization.join_all_meshes.label"))
row.operator("avatar_toolkit.join_selected_meshes", text=t("Optimization.join_selected_meshes.label"))
row.operator("avatar_toolkit.remove_doubles_safely", text="Remove Doubles Safely")
else:
layout.label(text="Please select an armature in Quick Access")
+4 -2
View File
@@ -7,6 +7,7 @@ from ..functions.translations import t
from ..core.import_pmx import import_pmx from ..core.import_pmx import import_pmx
from ..core.import_pmd import import_pmd from ..core.import_pmd import import_pmd
from ..core.importer import import_fbx from ..core.importer import import_fbx
from ..core.common import get_selected_armature, set_selected_armature
@register_wrap @register_wrap
class AvatarToolkitQuickAccessPanel(bpy.types.Panel): class AvatarToolkitQuickAccessPanel(bpy.types.Panel):
@@ -21,6 +22,9 @@ class AvatarToolkitQuickAccessPanel(bpy.types.Panel):
layout = self.layout layout = self.layout
layout.label(text=t("Quick_Access.options")) layout.label(text=t("Quick_Access.options"))
# Add Armature Selection
layout.prop(context.scene, "selected_armature", text="Select Armature")
row = layout.row() row = layout.row()
row.label(text=t("Quick_Access.import_export.label"), icon='IMPORT') row.label(text=t("Quick_Access.import_export.label"), icon='IMPORT')
@@ -129,5 +133,3 @@ class AVATAR_TOOLKIT_OT_export_fbx(bpy.types.Operator):
def execute(self, context): def execute(self, context):
bpy.ops.export_scene.fbx('INVOKE_DEFAULT') bpy.ops.export_scene.fbx('INVOKE_DEFAULT')
return {'FINISHED'} return {'FINISHED'}
+13 -7
View File
@@ -3,6 +3,7 @@ from ..core.register import register_wrap
from .panel import AvatarToolkitPanel from .panel import AvatarToolkitPanel
from bpy.types import Context from bpy.types import Context
from ..functions.translations import t from ..functions.translations import t
from ..core.common import get_selected_armature
@register_wrap @register_wrap
class AvatarToolkitToolsPanel(bpy.types.Panel): class AvatarToolkitToolsPanel(bpy.types.Panel):
@@ -15,11 +16,16 @@ class AvatarToolkitToolsPanel(bpy.types.Panel):
def draw(self, context: Context): def draw(self, context: Context):
layout = self.layout layout = self.layout
layout.label(text=t("Tools.tools_title.label")) armature = get_selected_armature(context)
layout.separator(factor=0.5)
row = layout.row(align=True) if armature:
row.scale_y = 1.5 layout.label(text=t("Tools.tools_title.label"))
row.operator("avatar_toolkit.convert_to_resonite", text=t("Tools.convert_to_resonite.label")) layout.separator(factor=0.5)
row = layout.row(align=True)
row.operator("avatar_toolkit.remove_doubles_safely", text="Remove Doubles Safely") row = layout.row(align=True)
row.scale_y = 1.5
row.operator("avatar_toolkit.convert_to_resonite", text=t("Tools.convert_to_resonite.label"))
row = layout.row(align=True)
row.operator("avatar_toolkit.remove_doubles_safely", text="Remove Doubles Safely")
else:
layout.label(text="Please select an armature in Quick Access")