Initial VRM Conversion
VRM Conversion, converts the vrm armature and removes colliders as there are not used in Unity. There some bugs and i need to optimise it and etc. Also we need to remove root empty bone as it's useless in Unity. Ran out of time to finish it but proof of concept it works lol. However dont want to release it unto Alpha 4 as it need to be tested and i may seperate some things into different buttons but i have not decided.
This commit is contained in:
+67
-13
@@ -257,7 +257,7 @@ bone_names = {
|
||||
],
|
||||
}
|
||||
|
||||
# Add VRM bone name variations
|
||||
# Add VRM bone name variations
|
||||
bone_names.update({
|
||||
'hips': bone_names['hips'] + ['jbipchips', 'jhips', 'vrmhips'],
|
||||
'spine': bone_names['spine'] + ['jbipcspine', 'jspine', 'vrmspine'],
|
||||
@@ -266,19 +266,73 @@ bone_names.update({
|
||||
'neck': bone_names['neck'] + ['jbipcneck', 'jneck', 'vrmneck'],
|
||||
'head': bone_names['head'] + ['jbipchead', 'jhead', 'vrmhead'],
|
||||
|
||||
# VRM specific finger naming
|
||||
'thumb_0_l': bone_names['thumb_0_l'] + ['thumbmetacarpall', 'jthumb1l'],
|
||||
'index_0_l': bone_names['index_0_l'] + ['indexmetacarpall', 'jindex1l'],
|
||||
'middle_0_l': bone_names['middle_0_l'] + ['middlemetacarpall', 'jmiddle1l'],
|
||||
'ring_0_l': bone_names['ring_0_l'] + ['ringmetacarpall', 'jring1l'],
|
||||
'pinkie_0_l': bone_names['pinkie_0_l'] + ['littlemetacarpall', 'jlittle1l'],
|
||||
# VRM arms
|
||||
'left_shoulder': bone_names['left_shoulder'] + ['jbipllshoulder', 'jlshoulder'],
|
||||
'left_arm': bone_names['left_arm'] + ['jbiplupperarm', 'jlupperarm'],
|
||||
'left_elbow': bone_names['left_elbow'] + ['jbipllforearm', 'jlforearm'],
|
||||
'left_wrist': bone_names['left_wrist'] + ['jbipllhand', 'jlhand'],
|
||||
|
||||
# Mirror for right side
|
||||
'thumb_0_r': bone_names['thumb_0_r'] + ['thumbmetacarpalr', 'jthumb1r'],
|
||||
'index_0_r': bone_names['index_0_r'] + ['indexmetacarpalr', 'jindex1r'],
|
||||
'middle_0_r': bone_names['middle_0_r'] + ['middlemetacarpalr', 'jmiddle1r'],
|
||||
'ring_0_r': bone_names['ring_0_r'] + ['ringmetacarpalr', 'jring1r'],
|
||||
'pinkie_0_r': bone_names['pinkie_0_r'] + ['littlemetacarpalr', 'jlittle1r']
|
||||
'right_shoulder': bone_names['right_shoulder'] + ['jbiprlshoulder', 'jrshoulder'],
|
||||
'right_arm': bone_names['right_arm'] + ['jbiprrupperarm', 'jrupperarm'],
|
||||
'right_elbow': bone_names['right_elbow'] + ['jbiprrforearm', 'jrforearm'],
|
||||
'right_wrist': bone_names['right_wrist'] + ['jbiprrhand', 'jrhand'],
|
||||
|
||||
# VRM legs
|
||||
'left_leg': bone_names['left_leg'] + ['jbiplupperleg', 'jlupperleg'],
|
||||
'left_knee': bone_names['left_knee'] + ['jbipllowerleg', 'jllowerleg'],
|
||||
'left_ankle': bone_names['left_ankle'] + ['jbipllfoot', 'jlfoot'],
|
||||
'left_toe': bone_names['left_toe'] + ['jbiplltoe', 'jltoe'],
|
||||
|
||||
'right_leg': bone_names['right_leg'] + ['jbiprrupperleg', 'jrupperleg'],
|
||||
'right_knee': bone_names['right_knee'] + ['jbiprrlowerleg', 'jrlowerleg'],
|
||||
'right_ankle': bone_names['right_ankle'] + ['jbiprrfoot', 'jrfoot'],
|
||||
'right_toe': bone_names['right_toe'] + ['jbiprrtoe', 'jrtoe'],
|
||||
|
||||
# VRM eyes
|
||||
'left_eye': bone_names['left_eye'] + ['jbipcleye', 'jleye'],
|
||||
'right_eye': bone_names['right_eye'] + ['jbipcreye', 'jreye'],
|
||||
|
||||
# VRM fingers - Left
|
||||
'thumb_1_l': bone_names['thumb_1_l'] + ['jbipllthumb1', 'jlthumb1'],
|
||||
'thumb_2_l': bone_names['thumb_2_l'] + ['jbipllthumb2', 'jlthumb2'],
|
||||
'thumb_3_l': bone_names['thumb_3_l'] + ['jbipllthumb3', 'jlthumb3'],
|
||||
|
||||
'index_1_l': bone_names['index_1_l'] + ['jbipllindex1', 'jlindex1'],
|
||||
'index_2_l': bone_names['index_2_l'] + ['jbipllindex2', 'jlindex2'],
|
||||
'index_3_l': bone_names['index_3_l'] + ['jbipllindex3', 'jlindex3'],
|
||||
|
||||
'middle_1_l': bone_names['middle_1_l'] + ['jbipllmiddle1', 'jlmiddle1'],
|
||||
'middle_2_l': bone_names['middle_2_l'] + ['jbipllmiddle2', 'jlmiddle2'],
|
||||
'middle_3_l': bone_names['middle_3_l'] + ['jbipllmiddle3', 'jlmiddle3'],
|
||||
|
||||
'ring_1_l': bone_names['ring_1_l'] + ['jbipllring1', 'jlring1'],
|
||||
'ring_2_l': bone_names['ring_2_l'] + ['jbipllring2', 'jlring2'],
|
||||
'ring_3_l': bone_names['ring_3_l'] + ['jbipllring3', 'jlring3'],
|
||||
|
||||
'pinkie_1_l': bone_names['pinkie_1_l'] + ['jbipllpinky1', 'jlpinky1'],
|
||||
'pinkie_2_l': bone_names['pinkie_2_l'] + ['jbipllpinky2', 'jlpinky2'],
|
||||
'pinkie_3_l': bone_names['pinkie_3_l'] + ['jbipllpinky3', 'jlpinky3'],
|
||||
|
||||
# VRM fingers - Right
|
||||
'thumb_1_r': bone_names['thumb_1_r'] + ['jbiprthumb1', 'jrthumb1'],
|
||||
'thumb_2_r': bone_names['thumb_2_r'] + ['jbiprthumb2', 'jrthumb2'],
|
||||
'thumb_3_r': bone_names['thumb_3_r'] + ['jbiprthumb3', 'jrthumb3'],
|
||||
|
||||
'index_1_r': bone_names['index_1_r'] + ['jbiprindex1', 'jrindex1'],
|
||||
'index_2_r': bone_names['index_2_r'] + ['jbiprindex2', 'jrindex2'],
|
||||
'index_3_r': bone_names['index_3_r'] + ['jbiprindex3', 'jrindex3'],
|
||||
|
||||
'middle_1_r': bone_names['middle_1_r'] + ['jbiprmiddle1', 'jrmiddle1'],
|
||||
'middle_2_r': bone_names['middle_2_r'] + ['jbiprmiddle2', 'jrmiddle2'],
|
||||
'middle_3_r': bone_names['middle_3_r'] + ['jbiprmiddle3', 'jrmiddle3'],
|
||||
|
||||
'ring_1_r': bone_names['ring_1_r'] + ['jbiprring1', 'jrring1'],
|
||||
'ring_2_r': bone_names['ring_2_r'] + ['jbiprring2', 'jrring2'],
|
||||
'ring_3_r': bone_names['ring_3_r'] + ['jbiprring3', 'jrring3'],
|
||||
|
||||
'pinkie_1_r': bone_names['pinkie_1_r'] + ['jbiprpinky1', 'jrpinky1'],
|
||||
'pinkie_2_r': bone_names['pinkie_2_r'] + ['jbiprpinky2', 'jrpinky2'],
|
||||
'pinkie_3_r': bone_names['pinkie_3_r'] + ['jbiprpinky3', 'jrpinky3']
|
||||
})
|
||||
|
||||
# array taken from cats
|
||||
|
||||
@@ -608,6 +608,13 @@ class AvatarToolkitSceneProperties(PropertyGroup):
|
||||
update=update_log_level
|
||||
)
|
||||
|
||||
# VRM Conversion Properties
|
||||
vrm_remove_colliders: BoolProperty(
|
||||
name="Remove Colliders",
|
||||
description="Remove VRM collider bones during conversion",
|
||||
default=True
|
||||
)
|
||||
|
||||
def register() -> None:
|
||||
"""Register the Avatar Toolkit property group"""
|
||||
logger.info("Registering Avatar Toolkit properties")
|
||||
|
||||
@@ -0,0 +1,454 @@
|
||||
import bpy
|
||||
from typing import Dict, List, Optional, Tuple, Set
|
||||
from bpy.types import Object, Bone
|
||||
from .common import get_active_armature
|
||||
from .dictionaries import simplify_bonename, standard_bones, bone_hierarchy
|
||||
from .logging_setup import logger
|
||||
|
||||
|
||||
def detect_vrm_armature(armature: Object) -> bool:
|
||||
"""
|
||||
Detect if armature uses VRM bone naming conventions
|
||||
"""
|
||||
if not armature or armature.type != 'ARMATURE':
|
||||
return False
|
||||
|
||||
vrm_patterns = [
|
||||
'jbipchips', 'jbipcspine', 'jbipcchest', 'jbipcneck', 'jbipchead',
|
||||
'jbiprlshoulder', 'jbiprrupperarm', 'jbiprrforearm', 'jbiprrhand',
|
||||
'jbipllshoulder', 'jbiplupperarm', 'jbipllforearm', 'jbipllhand',
|
||||
'jbiprrupperleg', 'jbiprrlowerleg', 'jbiprrfoot', 'jbiprrtoe',
|
||||
'jbiplupperleg', 'jbipllowerleg', 'jbipllfoot', 'jbiplltoe',
|
||||
'jbipc', 'jbipr', 'jbipl'
|
||||
]
|
||||
|
||||
found_vrm_bones = 0
|
||||
for bone_name in armature.data.bones.keys():
|
||||
simplified_name = simplify_bonename(bone_name)
|
||||
if simplified_name.startswith('jbip') or any(pattern in simplified_name for pattern in vrm_patterns):
|
||||
found_vrm_bones += 1
|
||||
|
||||
# Consider it VRM if we find at least 5 VRM bones
|
||||
logger.debug(f"Found {found_vrm_bones} VRM bones in armature {armature.name}")
|
||||
return found_vrm_bones >= 5
|
||||
|
||||
|
||||
def get_vrm_to_unity_mapping() -> Dict[str, str]:
|
||||
"""
|
||||
Get mapping from VRM bone names to Unity humanoid bone names
|
||||
"""
|
||||
return {
|
||||
# Core structure
|
||||
'jbipchips': standard_bones['hips'],
|
||||
'jbipcspine': standard_bones['spine'],
|
||||
'jbipcchest': standard_bones['chest'],
|
||||
'jbipcupperchest': standard_bones.get('upper_chest', 'UpperChest'),
|
||||
'jbipcneck': standard_bones['neck'],
|
||||
'jbipchead': standard_bones['head'],
|
||||
|
||||
# Left arm
|
||||
'jbipllshoulder': standard_bones.get('left_shoulder', 'LeftShoulder'),
|
||||
'jbiplupperarm': standard_bones['left_arm'],
|
||||
'jbipllforearm': standard_bones['left_elbow'],
|
||||
'jbipllhand': standard_bones['left_wrist'],
|
||||
|
||||
# Right arm
|
||||
'jbiprlshoulder': standard_bones.get('right_shoulder', 'RightShoulder'),
|
||||
'jbiprrupperarm': standard_bones['right_arm'],
|
||||
'jbiprrforearm': standard_bones['right_elbow'],
|
||||
'jbiprrhand': standard_bones['right_wrist'],
|
||||
|
||||
# Left leg
|
||||
'jbiplupperleg': standard_bones['left_leg'],
|
||||
'jbipllowerleg': standard_bones['left_knee'],
|
||||
'jbipllfoot': standard_bones['left_ankle'],
|
||||
'jbiplltoe': standard_bones['left_toe'],
|
||||
|
||||
# Right leg
|
||||
'jbiprrupperleg': standard_bones['right_leg'],
|
||||
'jbiprrlowerleg': standard_bones['right_knee'],
|
||||
'jbiprrfoot': standard_bones['right_ankle'],
|
||||
'jbiprrtoe': standard_bones['right_toe'],
|
||||
|
||||
# Eyes
|
||||
'jbipcleye': standard_bones.get('left_eye', 'Eye.L'),
|
||||
'jbipcreye': standard_bones.get('right_eye', 'Eye.R'),
|
||||
|
||||
# Fingers - Left thumb
|
||||
'jbipllthumb1': standard_bones.get('thumb_1_l', 'Thumb1.L'),
|
||||
'jbipllthumb2': standard_bones.get('thumb_2_l', 'Thumb2.L'),
|
||||
'jbipllthumb3': standard_bones.get('thumb_3_l', 'Thumb3.L'),
|
||||
|
||||
# Fingers - Left index
|
||||
'jbipllindex1': standard_bones.get('index_1_l', 'Index1.L'),
|
||||
'jbipllindex2': standard_bones.get('index_2_l', 'Index2.L'),
|
||||
'jbipllindex3': standard_bones.get('index_3_l', 'Index3.L'),
|
||||
|
||||
# Fingers - Left middle
|
||||
'jbipllmiddle1': standard_bones.get('middle_1_l', 'Middle1.L'),
|
||||
'jbipllmiddle2': standard_bones.get('middle_2_l', 'Middle2.L'),
|
||||
'jbipllmiddle3': standard_bones.get('middle_3_l', 'Middle3.L'),
|
||||
|
||||
# Fingers - Left ring
|
||||
'jbipllring1': standard_bones.get('ring_1_l', 'Ring1.L'),
|
||||
'jbipllring2': standard_bones.get('ring_2_l', 'Ring2.L'),
|
||||
'jbipllring3': standard_bones.get('ring_3_l', 'Ring3.L'),
|
||||
|
||||
# Fingers - Left pinky
|
||||
'jbipllpinky1': standard_bones.get('pinkie_1_l', 'Pinky1.L'),
|
||||
'jbipllpinky2': standard_bones.get('pinkie_2_l', 'Pinky2.L'),
|
||||
'jbipllpinky3': standard_bones.get('pinkie_3_l', 'Pinky3.L'),
|
||||
|
||||
# Fingers - Right thumb
|
||||
'jbiprthumb1': standard_bones.get('thumb_1_r', 'Thumb1.R'),
|
||||
'jbiprthumb2': standard_bones.get('thumb_2_r', 'Thumb2.R'),
|
||||
'jbiprthumb3': standard_bones.get('thumb_3_r', 'Thumb3.R'),
|
||||
|
||||
# Fingers - Right index
|
||||
'jbiprindex1': standard_bones.get('index_1_r', 'Index1.R'),
|
||||
'jbiprindex2': standard_bones.get('index_2_r', 'Index2.R'),
|
||||
'jbiprindex3': standard_bones.get('index_3_r', 'Index3.R'),
|
||||
|
||||
# Fingers - Right middle
|
||||
'jbiprmiddle1': standard_bones.get('middle_1_r', 'Middle1.R'),
|
||||
'jbiprmiddle2': standard_bones.get('middle_2_r', 'Middle2.R'),
|
||||
'jbiprmiddle3': standard_bones.get('middle_3_r', 'Middle3.R'),
|
||||
|
||||
# Fingers - Right ring
|
||||
'jbiprring1': standard_bones.get('ring_1_r', 'Ring1.R'),
|
||||
'jbiprring2': standard_bones.get('ring_2_r', 'Ring2.R'),
|
||||
'jbiprring3': standard_bones.get('ring_3_r', 'Ring3.R'),
|
||||
|
||||
# Fingers - Right pinky
|
||||
'jbiprpinky1': standard_bones.get('pinkie_1_r', 'Pinky1.R'),
|
||||
'jbiprpinky2': standard_bones.get('pinkie_2_r', 'Pinky2.R'),
|
||||
'jbiprpinky3': standard_bones.get('pinkie_3_r', 'Pinky3.R'),
|
||||
}
|
||||
|
||||
|
||||
def find_vrm_bones_in_armature(armature: Object) -> Dict[str, str]:
|
||||
"""
|
||||
Find VRM bones in armature and return mapping to their actual names
|
||||
"""
|
||||
vrm_mapping = get_vrm_to_unity_mapping()
|
||||
found_bones = {}
|
||||
|
||||
for bone_name in armature.data.bones.keys():
|
||||
simplified_name = simplify_bonename(bone_name)
|
||||
|
||||
# Check if this bone matches any VRM pattern
|
||||
for vrm_pattern, unity_name in vrm_mapping.items():
|
||||
if simplified_name == vrm_pattern:
|
||||
found_bones[bone_name] = unity_name
|
||||
logger.debug(f"Found VRM bone: {bone_name} -> {unity_name}")
|
||||
break
|
||||
|
||||
if simplified_name.startswith('jbip') and bone_name not in found_bones:
|
||||
unity_equivalent = guess_unity_name_from_vrm(simplified_name)
|
||||
if unity_equivalent:
|
||||
found_bones[bone_name] = unity_equivalent
|
||||
logger.debug(f"Guessed VRM bone mapping: {bone_name} -> {unity_equivalent}")
|
||||
|
||||
return found_bones
|
||||
|
||||
|
||||
def guess_unity_name_from_vrm(vrm_simplified: str) -> Optional[str]:
|
||||
"""
|
||||
Attempt to guess Unity bone name from VRM simplified name
|
||||
"""
|
||||
# Map common VRM patterns to Unity equivalents
|
||||
pattern_mappings = {
|
||||
'jbipcupperchest': 'UpperChest',
|
||||
'jbipcchest': 'Chest',
|
||||
'jbipcspine': 'Spine',
|
||||
'jbipchips': 'Hips',
|
||||
'jbipcneck': 'Neck',
|
||||
'jbipchead': 'Head',
|
||||
|
||||
# Left arm
|
||||
'jbipllclavicle': 'LeftShoulder',
|
||||
'jbipllshoulder': 'LeftShoulder',
|
||||
'jbiplupperarm': 'LeftUpperArm',
|
||||
'jbipllforearm': 'LeftLowerArm',
|
||||
'jbipllhand': 'LeftHand',
|
||||
|
||||
# Right arm
|
||||
'jbiprrclavicle': 'RightShoulder',
|
||||
'jbiprlshoulder': 'RightShoulder',
|
||||
'jbiprrupperarm': 'RightUpperArm',
|
||||
'jbiprrforearm': 'RightLowerArm',
|
||||
'jbiprrhand': 'RightHand',
|
||||
|
||||
# Left leg
|
||||
'jbiplupperleg': 'LeftUpperLeg',
|
||||
'jbipllowerleg': 'LeftLowerLeg',
|
||||
'jbipllfoot': 'LeftFoot',
|
||||
'jbiplltoe': 'LeftToes',
|
||||
|
||||
# Right leg
|
||||
'jbiprrupperleg': 'RightUpperLeg',
|
||||
'jbiprrlowerleg': 'RightLowerLeg',
|
||||
'jbiprrfoot': 'RightFoot',
|
||||
'jbiprrtoe': 'RightToes',
|
||||
|
||||
# Eyes
|
||||
'jbipcleye': 'LeftEye',
|
||||
'jbipcreye': 'RightEye'
|
||||
}
|
||||
|
||||
return pattern_mappings.get(vrm_simplified)
|
||||
|
||||
|
||||
def is_vrm_collider_object(obj_name: str) -> bool:
|
||||
"""
|
||||
Test if an object name represents a VRM collider
|
||||
"""
|
||||
obj_name_lower = obj_name.lower()
|
||||
collider_patterns = ['collider', 'collision', 'dynamic', 'spring', 'physics', 'secondary']
|
||||
|
||||
# Must contain a collider pattern
|
||||
contains_collider = any(pattern in obj_name_lower for pattern in collider_patterns)
|
||||
if not contains_collider:
|
||||
return False
|
||||
|
||||
# Must be VRM-related (multiple detection methods)
|
||||
is_vrm = (
|
||||
'j_bip' in obj_name_lower or
|
||||
'jbip' in simplify_bonename(obj_name) or
|
||||
any(vrm_part in obj_name_lower for vrm_part in ['j_bip_c_', 'j_bip_l_', 'j_bip_r_'])
|
||||
)
|
||||
|
||||
return is_vrm
|
||||
|
||||
|
||||
def remove_vrm_colliders(armature: Object = None) -> Tuple[int, List[str]]:
|
||||
"""
|
||||
Simple approach: Remove ALL objects with 'collider' in their name and clean up empty collections
|
||||
Returns tuple of (removed_count, removed_object_names)
|
||||
"""
|
||||
objects_to_remove = []
|
||||
removed_names = []
|
||||
collections_to_check = set()
|
||||
|
||||
# Store the current mode and active object
|
||||
current_mode = bpy.context.mode
|
||||
original_active = bpy.context.view_layer.objects.active
|
||||
|
||||
if current_mode != 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
try:
|
||||
logger.info("Starting simple collider removal - removing ALL objects with 'collider' in name")
|
||||
|
||||
collider_object_names = []
|
||||
for obj in bpy.data.objects:
|
||||
if 'collider' in obj.name.lower():
|
||||
collider_object_names.append(obj.name)
|
||||
# Track collections this object is in
|
||||
for collection in obj.users_collection:
|
||||
collections_to_check.add(collection)
|
||||
logger.info(f"Found collider object: {obj.name}")
|
||||
|
||||
logger.info(f"Found {len(collider_object_names)} collider objects to remove")
|
||||
|
||||
# Remove collider objects by name
|
||||
removed_count = 0
|
||||
for obj_name in collider_object_names:
|
||||
try:
|
||||
# Check if object still exists
|
||||
if obj_name in bpy.data.objects:
|
||||
obj = bpy.data.objects[obj_name]
|
||||
logger.info(f"Removing collider object: {obj_name}")
|
||||
|
||||
# Remove from all collections first
|
||||
for collection in list(obj.users_collection):
|
||||
collection.objects.unlink(obj)
|
||||
logger.debug(f" Unlinked from collection: {collection.name}")
|
||||
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
removed_count += 1
|
||||
removed_names.append(obj_name)
|
||||
logger.info(f" Successfully removed: {obj_name}")
|
||||
else:
|
||||
logger.debug(f"Object {obj_name} already removed")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to remove collider object {obj_name}: {str(e)}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
logger.info(f"Successfully removed {removed_count} collider objects")
|
||||
|
||||
# Clean up empty collections
|
||||
empty_collections_removed = 0
|
||||
for collection in list(collections_to_check):
|
||||
try:
|
||||
# Check if collection is now empty and not the master collection
|
||||
if (len(collection.objects) == 0 and
|
||||
len(collection.children) == 0 and
|
||||
collection.name != "Collection" and
|
||||
collection.name != "Master Collection"):
|
||||
|
||||
logger.info(f"Removing empty collection: {collection.name}")
|
||||
|
||||
if collection in bpy.context.scene.collection.children:
|
||||
bpy.context.scene.collection.children.unlink(collection)
|
||||
|
||||
bpy.data.collections.remove(collection)
|
||||
empty_collections_removed += 1
|
||||
logger.info(f" Successfully removed collection: {collection.name}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to remove empty collection {collection.name}: {str(e)}")
|
||||
|
||||
if empty_collections_removed > 0:
|
||||
logger.info(f"Cleaned up {empty_collections_removed} empty collections")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during collider removal: {str(e)}")
|
||||
return 0, []
|
||||
|
||||
finally:
|
||||
if original_active and original_active.name in bpy.data.objects:
|
||||
bpy.context.view_layer.objects.active = original_active
|
||||
|
||||
if current_mode != 'OBJECT':
|
||||
try:
|
||||
bpy.ops.object.mode_set(mode=current_mode)
|
||||
except:
|
||||
pass
|
||||
|
||||
logger.info(f"Collider removal complete. Removed {len(removed_names)} objects")
|
||||
return len(removed_names), removed_names
|
||||
|
||||
|
||||
def convert_vrm_to_unity(armature: Object, remove_colliders: bool = True) -> Tuple[bool, List[str], int]:
|
||||
"""
|
||||
Convert VRM armature bone names to Unity humanoid format
|
||||
|
||||
Returns:
|
||||
Tuple of (success, messages, converted_count)
|
||||
"""
|
||||
if not armature or armature.type != 'ARMATURE':
|
||||
return False, ["No valid armature selected"], 0
|
||||
|
||||
logger.info(f"Starting VRM to Unity conversion for armature: {armature.name}")
|
||||
|
||||
# Check if this is a VRM armature
|
||||
if not detect_vrm_armature(armature):
|
||||
return False, ["Selected armature does not appear to be a VRM armature"], 0
|
||||
|
||||
messages = []
|
||||
converted_count = 0
|
||||
failed_conversions = []
|
||||
collider_count = 0
|
||||
|
||||
current_mode = bpy.context.mode
|
||||
if current_mode != 'EDIT':
|
||||
bpy.context.view_layer.objects.active = armature
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
try:
|
||||
# First, remove collider objects and bones if requested
|
||||
if remove_colliders:
|
||||
collider_count, removed_colliders = remove_vrm_colliders(armature)
|
||||
if collider_count > 0:
|
||||
messages.append(f"Removed {collider_count} VRM collider objects/bones")
|
||||
logger.info(f"Removed {collider_count} VRM colliders: {removed_colliders}")
|
||||
|
||||
vrm_bones = find_vrm_bones_in_armature(armature)
|
||||
|
||||
if not vrm_bones:
|
||||
if remove_colliders and collider_count > 0:
|
||||
messages.append("No VRM bones found to convert (colliders were removed)")
|
||||
return True, messages, 0
|
||||
else:
|
||||
return False, ["No VRM bones found in armature"], 0
|
||||
|
||||
if bpy.context.mode != 'EDIT':
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
# Rename bones
|
||||
for vrm_bone_name, unity_name in vrm_bones.items():
|
||||
if vrm_bone_name in armature.data.edit_bones:
|
||||
bone = armature.data.edit_bones[vrm_bone_name]
|
||||
|
||||
# Check if target name already exists
|
||||
if unity_name in armature.data.edit_bones and unity_name != vrm_bone_name:
|
||||
failed_conversions.append(f"{vrm_bone_name} -> {unity_name} (name conflict)")
|
||||
continue
|
||||
|
||||
# Rename the bone
|
||||
bone.name = unity_name
|
||||
converted_count += 1
|
||||
logger.debug(f"Renamed bone: {vrm_bone_name} -> {unity_name}")
|
||||
|
||||
messages.append(f"Successfully converted {converted_count} VRM bones to Unity format")
|
||||
|
||||
if failed_conversions:
|
||||
messages.append("Failed conversions due to name conflicts:")
|
||||
messages.extend(failed_conversions)
|
||||
|
||||
logger.info(f"VRM to Unity conversion completed. Converted {converted_count} bones")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during VRM conversion: {str(e)}")
|
||||
messages.append(f"Error during conversion: {str(e)}")
|
||||
return False, messages, converted_count
|
||||
|
||||
finally:
|
||||
# Restore original mode
|
||||
if current_mode != 'EDIT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
return converted_count > 0 or (remove_colliders and collider_count > 0), messages, converted_count
|
||||
|
||||
|
||||
def validate_unity_hierarchy(armature: Object) -> Tuple[bool, List[str]]:
|
||||
"""
|
||||
Validate that the converted armature has proper Unity humanoid hierarchy
|
||||
"""
|
||||
if not armature or armature.type != 'ARMATURE':
|
||||
return False, ["No valid armature to validate"]
|
||||
|
||||
messages = []
|
||||
is_valid = True
|
||||
|
||||
# Check for essential Unity bones
|
||||
essential_unity_bones = [
|
||||
standard_bones['hips'],
|
||||
standard_bones['spine'],
|
||||
standard_bones['chest'],
|
||||
standard_bones['neck'],
|
||||
standard_bones['head']
|
||||
]
|
||||
|
||||
missing_bones = []
|
||||
for bone_name in essential_unity_bones:
|
||||
if bone_name not in armature.data.bones:
|
||||
missing_bones.append(bone_name)
|
||||
|
||||
if missing_bones:
|
||||
is_valid = False
|
||||
messages.append("Missing essential Unity bones:")
|
||||
messages.extend([f"- {bone}" for bone in missing_bones])
|
||||
|
||||
# Validate basic hierarchy
|
||||
hierarchy_issues = []
|
||||
for parent_name, child_name in bone_hierarchy:
|
||||
if parent_name in armature.data.bones and child_name in armature.data.bones:
|
||||
parent_bone = armature.data.bones[parent_name]
|
||||
child_bone = armature.data.bones[child_name]
|
||||
|
||||
if child_bone.parent != parent_bone:
|
||||
hierarchy_issues.append(f"{parent_name} -> {child_name}")
|
||||
|
||||
if hierarchy_issues:
|
||||
is_valid = False
|
||||
messages.append("Hierarchy issues found:")
|
||||
messages.extend([f"- {issue}" for issue in hierarchy_issues])
|
||||
|
||||
if is_valid:
|
||||
messages.append("Unity hierarchy validation passed")
|
||||
|
||||
return is_valid, messages
|
||||
Reference in New Issue
Block a user