Fixes
This commit is contained in:
@@ -14,3 +14,9 @@ blender_version_min = "4.3.0"
|
|||||||
license = [
|
license = [
|
||||||
"SPDX:GPL-3.0-or-later",
|
"SPDX:GPL-3.0-or-later",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
wheels = [
|
||||||
|
"lz4-4.3.3-cp311-cp311-macosx_11_0_arm64.whl",
|
||||||
|
"lz4-4.3.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
|
||||||
|
"lz4-4.3.3-cp311-cp311-win_amd64.whl"
|
||||||
|
]
|
||||||
|
|||||||
+61
-94
@@ -3,20 +3,16 @@ import bpy
|
|||||||
import bpy_extras
|
import bpy_extras
|
||||||
from numpy import double
|
from numpy import double
|
||||||
|
|
||||||
from .common import get_armature, get_selected_armature, simplify_bonename, is_valid_armature
|
from .common import get_active_armature, simplify_bonename, validate_armature
|
||||||
from bpy.types import Object, ShapeKey, Mesh, Context, Operator
|
from bpy.types import Object, ShapeKey, Mesh, Context, Operator
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from ..core.register import register_wrap
|
from ..core.translations import t
|
||||||
from ..functions.translations import t
|
from ..core.dictionaries import bone_names, resonite_translations
|
||||||
from ..core.dictionaries import bone_names
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from .resonite_loader import resonite_animx, resonite_types
|
from .resonite_loader import resonite_animx, resonite_types
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@register_wrap
|
|
||||||
class AvatarToolKit_OT_ExportResonite(Operator):
|
class AvatarToolKit_OT_ExportResonite(Operator):
|
||||||
bl_idname = 'avatar_toolkit.export_resonite'
|
bl_idname = 'avatar_toolkit.export_resonite'
|
||||||
bl_label = t("Importer.export_resonite.label")
|
bl_label = t("Importer.export_resonite.label")
|
||||||
@@ -24,15 +20,13 @@ class AvatarToolKit_OT_ExportResonite(Operator):
|
|||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
filepath: bpy.props.StringProperty()
|
filepath: bpy.props.StringProperty()
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context: Context):
|
def poll(cls, context: Context):
|
||||||
if get_armature(context) is None:
|
if get_active_armature(context) is None:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def execute(self, context: Context):
|
def execute(self, context: Context):
|
||||||
#settings stolen from cats.
|
|
||||||
bpy.ops.export_scene.gltf('INVOKE_AREA',
|
bpy.ops.export_scene.gltf('INVOKE_AREA',
|
||||||
export_image_format = 'WEBP',
|
export_image_format = 'WEBP',
|
||||||
export_image_quality = 75,
|
export_image_quality = 75,
|
||||||
@@ -43,109 +37,83 @@ class AvatarToolKit_OT_ExportResonite(Operator):
|
|||||||
export_nla_strips = True)
|
export_nla_strips = True)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@register_wrap
|
class AvatarToolkit_OT_ConvertResonite(Operator):
|
||||||
class AvatarToolKit_OT_ConvertToResonite(Operator):
|
"""Convert armature bone names to Resonite format with progress tracking and validation"""
|
||||||
bl_idname = 'avatar_toolkit.convert_to_resonite'
|
bl_idname = "avatar_toolkit.convert_resonite"
|
||||||
bl_label = t('Tools.convert_to_resonite.label')
|
bl_label = t("Tools.convert_resonite")
|
||||||
bl_description = t('Tools.convert_to_resonite.desc')
|
bl_description = t("Tools.convert_resonite_desc")
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context: Context) -> bool:
|
def poll(cls, context: Context) -> bool:
|
||||||
armature = get_selected_armature(context)
|
armature = get_active_armature(context)
|
||||||
return armature is not None and is_valid_armature(armature)
|
|
||||||
|
|
||||||
def execute(self, context: Context) -> set:
|
|
||||||
armature = get_selected_armature(context)
|
|
||||||
if not armature:
|
if not armature:
|
||||||
self.report({'WARNING'}, t("Tools.no_armature_selected"))
|
return False
|
||||||
|
is_valid, _ = validate_armature(armature)
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
def execute(self, context: Context) -> Set[str]:
|
||||||
|
armature = get_active_armature(context)
|
||||||
|
if not armature:
|
||||||
|
logger.warning("No armature selected for Resonite conversion")
|
||||||
|
self.report({'WARNING'}, t("Armature.validation.no_armature"))
|
||||||
return {'CANCELLED'}
|
return {'CANCELLED'}
|
||||||
|
|
||||||
translate_bone_fails = 0
|
translate_bone_fails: int = 0
|
||||||
untranslated_bones = set()
|
untranslated_bones: Set[str] = set()
|
||||||
|
simplified_names: Dict[str, str] = {}
|
||||||
|
|
||||||
reverse_bone_lookup = dict()
|
# Create reverse lookup dictionary
|
||||||
for (preferred_name, name_list) in bone_names.items():
|
reverse_bone_lookup = {}
|
||||||
|
for preferred_name, name_list in bone_names.items():
|
||||||
for name in name_list:
|
for name in name_list:
|
||||||
reverse_bone_lookup[name] = preferred_name
|
reverse_bone_lookup[name] = preferred_name
|
||||||
|
|
||||||
resonite_translations = {
|
try:
|
||||||
'hips': "Hips",
|
context.view_layer.objects.active = armature
|
||||||
'spine': "Spine",
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
'chest': "Chest",
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
'neck': "Neck",
|
|
||||||
'head': "Head",
|
|
||||||
'left_eye': "Eye.L",
|
|
||||||
'right_eye': "Eye.R",
|
|
||||||
'right_leg': "UpperLeg.R",
|
|
||||||
'right_knee': "Calf.R",
|
|
||||||
'right_ankle': "Foot.R",
|
|
||||||
'right_toe': 'Toes.R',
|
|
||||||
'right_shoulder': "Shoulder.R",
|
|
||||||
'right_arm': "UpperArm.R",
|
|
||||||
'right_elbow': "ForeArm.R",
|
|
||||||
'right_wrist': "Hand.R",
|
|
||||||
'left_leg': "UpperLeg.L",
|
|
||||||
'left_knee': "Calf.L",
|
|
||||||
'left_ankle': "Foot.L",
|
|
||||||
'left_toe': "Toes.L",
|
|
||||||
'left_shoulder': "Shoulder.L",
|
|
||||||
'left_arm': "UpperArm.L",
|
|
||||||
'left_elbow': "ForeArm.L",
|
|
||||||
'left_wrist': "Hand.R",
|
|
||||||
|
|
||||||
'pinkie_1_l': "pinkie1.L",
|
# Cache simplified bone names
|
||||||
'pinkie_2_l': "pinkie2.L",
|
for bone in armature.data.bones:
|
||||||
'pinkie_3_l': "pinkie3.L",
|
simplified_names[bone.name] = simplify_bonename(bone.name)
|
||||||
'ring_1_l': "ring1.L",
|
|
||||||
'ring_2_l': "ring2.L",
|
|
||||||
'ring_3_l': "ring3.L",
|
|
||||||
'middle_1_l': "middle1.L",
|
|
||||||
'middle_2_l': "middle2.L",
|
|
||||||
'middle_3_l': "middle3.L",
|
|
||||||
'index_1_l': "index1.L",
|
|
||||||
'index_2_l': "index2.L",
|
|
||||||
'index_3_l': "index3.L",
|
|
||||||
'thumb_1_l': "thumb1.L",
|
|
||||||
'thumb_2_l': "thumb2.L",
|
|
||||||
'thumb_3_l': "thumb3.L",
|
|
||||||
|
|
||||||
'pinkie_1_r': "pinkie1.R",
|
total_bones = len(armature.data.bones)
|
||||||
'pinkie_2_r': "pinkie2.R",
|
with ProgressTracker(context, total_bones, t("Tools.convert_resonite.operation")) as progress:
|
||||||
'pinkie_3_r': "pinkie3.R",
|
for bone in armature.data.bones:
|
||||||
'ring_1_r': "ring1.R",
|
# Remove any existing "<noik>" tags
|
||||||
'ring_2_r': "ring2.R",
|
bone.name = re.compile(re.escape("<noik>"), re.IGNORECASE).sub("", bone.name)
|
||||||
'ring_3_r': "ring3.R",
|
simplified_name = simplified_names[bone.name]
|
||||||
'middle_1_r': "middle1.R",
|
|
||||||
'middle_2_r': "middle2.R",
|
|
||||||
'middle_3_r': "middle3.R",
|
|
||||||
'index_1_r': "index1.R",
|
|
||||||
'index_2_r': "index2.R",
|
|
||||||
'index_3_r': "index3.R",
|
|
||||||
'thumb_1_r': "thumb1.R",
|
|
||||||
'thumb_2_r': "thumb2.R",
|
|
||||||
'thumb_3_r': "thumb3.R"
|
|
||||||
}
|
|
||||||
|
|
||||||
context.view_layer.objects.active = armature
|
if simplified_name in reverse_bone_lookup and reverse_bone_lookup[simplified_name] in resonite_translations:
|
||||||
bpy.ops.object.mode_set(mode='EDIT')
|
new_name = resonite_translations[reverse_bone_lookup[simplified_name]]
|
||||||
|
logger.debug(f"Translating bone: {bone.name} -> {new_name}")
|
||||||
|
bone.name = new_name
|
||||||
|
else:
|
||||||
|
untranslated_bones.add(bone.name)
|
||||||
|
bone.name = bone.name + "<noik>"
|
||||||
|
translate_bone_fails += 1
|
||||||
|
logger.debug(f"Failed to translate bone: {bone.name}")
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
progress.step(t("Tools.convert_resonite.processing", name=bone.name))
|
||||||
bone.name = re.compile(re.escape("<noik>"), re.IGNORECASE).sub("",bone.name) #remove "NOIK" from bones before translating again, in case an update was done that fixes a translation.
|
|
||||||
for bone in armature.data.bones:
|
|
||||||
if simplify_bonename(bone.name) in reverse_bone_lookup and reverse_bone_lookup[simplify_bonename(bone.name)] in resonite_translations:
|
|
||||||
bone.name = resonite_translations[reverse_bone_lookup[simplify_bonename(bone.name)]]
|
|
||||||
else:
|
|
||||||
untranslated_bones.add(bone.name)
|
|
||||||
|
|
||||||
bone.name = bone.name+"<noik>"
|
except Exception as e:
|
||||||
translate_bone_fails += 1
|
logger.error(f"Error during Resonite conversion: {str(e)}")
|
||||||
|
self.report({'ERROR'}, str(e))
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
finally:
|
||||||
|
try:
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error returning to object mode: {str(e)}")
|
||||||
|
|
||||||
if translate_bone_fails > 0:
|
if translate_bone_fails > 0:
|
||||||
self.report({'INFO'}, t("Tools.bones_translated_with_fails").format(translate_bone_fails=translate_bone_fails))
|
logger.info(f"Conversion completed with {translate_bone_fails} untranslated bones")
|
||||||
|
logger.debug(f"Untranslated bones: {untranslated_bones}")
|
||||||
|
self.report({'INFO'}, t("Tools.bones_translated_with_fails", translate_bone_fails=translate_bone_fails))
|
||||||
else:
|
else:
|
||||||
|
logger.info("All bones translated successfully")
|
||||||
self.report({'INFO'}, t("Tools.bones_translated_success"))
|
self.report({'INFO'}, t("Tools.bones_translated_success"))
|
||||||
|
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
@@ -159,7 +127,6 @@ def makeorexistingfcurve(action: bpy.types.Action,data_path: str,action_group: s
|
|||||||
print("fcurve with data \""+data_path+"\" already exists")
|
print("fcurve with data \""+data_path+"\" already exists")
|
||||||
return fcurve
|
return fcurve
|
||||||
|
|
||||||
@register_wrap
|
|
||||||
class AvatarToolKit_OT_AnimX_Importer(Operator,bpy_extras.io_utils.ImportHelper):
|
class AvatarToolKit_OT_AnimX_Importer(Operator,bpy_extras.io_utils.ImportHelper):
|
||||||
bl_idname = 'avatar_toolkit.animx_importer'
|
bl_idname = 'avatar_toolkit.animx_importer'
|
||||||
bl_label = t('Tools.animx_importer.label')
|
bl_label = t('Tools.animx_importer.label')
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user