Armature Selection Improvements.

- Added a check to make sure Armature is valid.
- Added a helper to select the current armature  selected in armature selection.
- Added a helper to get all meshes.
- Updated all current functions to work with the system.
This commit is contained in:
Yusarina
2024-07-24 00:27:14 +01:00
parent 06c7cff4b7
commit 76046f7c6d
6 changed files with 61 additions and 30 deletions
+25 -1
View File
@@ -63,9 +63,33 @@ def get_armatures(self, context):
def get_selected_armature(context): def get_selected_armature(context):
if context.scene.selected_armature: if context.scene.selected_armature:
return bpy.data.objects.get(context.scene.selected_armature) armature = bpy.data.objects.get(context.scene.selected_armature)
if is_valid_armature(armature):
return armature
return None return None
def set_selected_armature(context, armature): def set_selected_armature(context, armature):
context.scene.selected_armature = armature.name if armature else "" context.scene.selected_armature = armature.name if armature else ""
def is_valid_armature(armature: Object) -> bool:
if not armature or armature.type != 'ARMATURE':
return False
if not armature.data or not armature.data.bones:
return False
return True
def select_current_armature(context):
armature = get_selected_armature(context)
if armature:
bpy.ops.object.select_all(action='DESELECT')
armature.select_set(True)
context.view_layer.objects.active = armature
return True
return False
def get_all_meshes(context: Context) -> List[Object]:
armature = get_selected_armature(context)
if armature and is_valid_armature(armature):
return [obj for obj in bpy.data.objects if obj.type == 'MESH' and obj.parent == armature]
return []
+18 -15
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, get_selected_armature from ..core.common import clean_material_names, get_selected_armature, is_valid_armature, get_all_meshes
from ..core.register import register_wrap from ..core.register import register_wrap
from ..functions.translations import t from ..functions.translations import t
@@ -65,46 +65,49 @@ class CombineMaterials(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return context.active_object is not None and get_selected_armature(context) is not None armature = get_selected_armature(context)
return armature is not None and is_valid_armature(armature)
def execute(self, context: Context) -> set: def execute(self, context: Context) -> set:
bpy.ops.object.mode_set(mode='OBJECT')
armature = get_selected_armature(context) armature = get_selected_armature(context)
if not armature: if not armature:
self.report({'WARNING'}, "No armature selected") 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] context.view_layer.objects.active = armature
bpy.ops.object.mode_set(mode='OBJECT')
meshes = get_all_meshes(context)
if not meshes: if not meshes:
self.report({'WARNING'}, "No meshes found for the selected armature") self.report({'WARNING'}, "No meshes found for the selected armature")
return {'CANCELLED'} return {'CANCELLED'}
bpy.ops.object.mode_set(mode='OBJECT')
self.consolidate_materials(meshes) self.consolidate_materials(meshes)
self.remove_unused_materials() self.remove_unused_materials()
self.cleanmatslots() self.cleanmatslots()
self.clean_material_names() self.clean_material_names()
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.view_layer.objects.active = armature
return {'FINISHED'} return {'FINISHED'}
def consolidate_materials(self, objects: List[Object]) -> None: def consolidate_materials(self, meshes: List[Object]) -> None:
mat_mapping: dict = {} mat_mapping: dict = {}
num_combined: int = 0 num_combined: int = 0
for ob in objects: for mesh in meshes:
for slot in ob.material_slots: for slot in mesh.material_slots:
mat: Optional[Material] = slot.material mat: Optional[Material] = slot.material
if mat: if mat:
base_name: str = get_base_name(mat.name) base_name: str = get_base_name(mat.name)
if base_name in mat_mapping: if base_name in mat_mapping:
base_mat: Material = mat_mapping[base_name] base_mat: Material = mat_mapping[base_name]
if materials_match(base_mat, mat): try:
consolidate_textures(base_mat, mat) if materials_match(base_mat, mat):
num_combined += 1 consolidate_textures(base_mat, mat)
slot.material = base_mat num_combined += 1
slot.material = base_mat
except AttributeError:
# Skip this material if there's an attribute mismatch
continue
else: else:
mat_mapping[base_name] = mat mat_mapping[base_name] = mat
+8 -5
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, get_selected_armature from ..core.common import fix_uv_coordinates, get_selected_armature, is_valid_armature, select_current_armature, get_all_meshes
from ..functions.translations import t from ..functions.translations import t
@register_wrap @register_wrap
@@ -14,22 +14,23 @@ class JoinAllMeshes(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return context.mode == 'OBJECT' and get_selected_armature(context) is not None armature = get_selected_armature(context)
return armature is not None and is_valid_armature(armature)
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:
armature = get_selected_armature(context) if not select_current_armature(context):
if not armature:
self.report({'WARNING'}, "No armature selected") self.report({'WARNING'}, "No armature selected")
return return
armature = get_selected_armature(context)
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' and 'Armature' in obj.modifiers and obj.modifiers['Armature'].object == armature] meshes: List[Object] = get_all_meshes(context)
for mesh in meshes: for mesh in meshes:
mesh.select_set(True) mesh.select_set(True)
@@ -44,6 +45,8 @@ class JoinAllMeshes(Operator):
else: else:
self.report({'WARNING'}, "No mesh objects selected") self.report({'WARNING'}, "No mesh objects selected")
context.view_layer.objects.active = armature
@register_wrap @register_wrap
class JoinSelectedMeshes(Operator): class JoinSelectedMeshes(Operator):
bl_idname = "avatar_toolkit.join_selected_meshes" bl_idname = "avatar_toolkit.join_selected_meshes"
+6 -5
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_selected_armature from ..core.common import get_selected_armature, is_valid_armature, select_current_armature, get_all_meshes
class meshEntry(TypedDict): class meshEntry(TypedDict):
@@ -23,17 +23,18 @@ class RemoveDoublesSafely(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return context.mode == 'OBJECT' and get_selected_armature(context) is not None armature = get_selected_armature(context)
return armature is not None and is_valid_armature(armature)
def execute(self, context: Context) -> set: def execute(self, context: Context) -> set:
armature = get_selected_armature(context) if not select_current_armature(context):
if not armature:
self.report({'WARNING'}, "No armature selected") self.report({'WARNING'}, "No armature selected")
return {'CANCELLED'} return {'CANCELLED'}
armature = get_selected_armature(context)
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] = [obj for obj in armature.children if obj.type == 'MESH'] objects: List[Object] = get_all_meshes(context)
for mesh in objects: for mesh in objects:
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]:
+3 -2
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_selected_armature, simplify_bonename from ..core.common import get_selected_armature, simplify_bonename, is_valid_armature
from ..functions.translations import t from ..functions.translations import t
@register_wrap @register_wrap
@@ -16,7 +16,8 @@ class ConvertToResonite(Operator):
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return get_selected_armature(context) is not None armature = get_selected_armature(context)
return armature is not None and is_valid_armature(armature)
def execute(self, context: Context) -> set: def execute(self, context: Context) -> set:
armature = get_selected_armature(context) armature = get_selected_armature(context)
-1
View File
@@ -22,7 +22,6 @@ 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") layout.prop(context.scene, "selected_armature", text="Select Armature")
row = layout.row() row = layout.row()