From 758cf0e7609afe697469361ec82baf6d0ce0374e Mon Sep 17 00:00:00 2001 From: 989onan Date: Sat, 14 Dec 2024 18:21:32 -0500 Subject: [PATCH] almost there with this one, LZMA and LZ4 don't work --- .vscode/settings.json | 1 + core/common.py | 65 ++- core/export_resonite.py | 36 -- core/importer.py | 1 + core/preferences.json | 3 +- core/resonite_loader/common.py | 56 +++ core/resonite_loader/resonite_animx.py | 472 ++++++++++++++++++ core/resonite_loader/resonite_types.py | 655 +++++++++++++++++++++++++ core/resonite_utils.py | 114 ++++- functions/resonite_functions.py | 115 ----- functions/uv_tools.py | 3 + 11 files changed, 1356 insertions(+), 165 deletions(-) delete mode 100644 core/export_resonite.py create mode 100644 core/resonite_loader/common.py create mode 100644 core/resonite_loader/resonite_animx.py create mode 100644 core/resonite_loader/resonite_types.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 2021d12..37a3b13 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "D:\\SteamLibrary\\steamapps\\common\\Blender\\4.3\\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 "D:\\blender stuff\\blendercodestuff\\4.3", + "D:\\SteamLibrary\\steamapps\\common\\Blender\\4.3\\python\\lib\\site-packages", "/Users/frankche/Documents/blendercoding/4.1/", "/Users/frankche/Library/Application Support/Blender/4.3/extensions/user_default/" ], diff --git a/core/common.py b/core/common.py index 30cd235..bc06b41 100644 --- a/core/common.py +++ b/core/common.py @@ -1,11 +1,14 @@ import bpy + import numpy as np -from .dictionaries import bone_names import threading import time import webbrowser import typing +import struct +from io import BytesIO +from .dictionaries import bone_names from ..core.register import register_wrap from typing import List, Optional, Tuple from bpy.types import Object, ShapeKey, Mesh, Context, Material, PropertyGroup @@ -497,3 +500,63 @@ def transfer_vertex_weights(context: Context, obj: bpy.types.Object, source_grou return True +#Binary tools + +import ctypes +def ReadCSharp_str(data: BytesIO) -> str: + return data.read(read7bitEncoded_int(data)).decode('utf-16-le') + +def WriteCSharp_str(data: BytesIO, string: str) -> str: + write7bitEncoded_int(len(string)*2) + return data.write(string.encode("utf-16-le")) + +def read7bitEncoded_ulong(data: BytesIO) -> np.int64: + num: ctypes.c_uint = ctypes.c_uint(0) + num2: int = 0 + flag: bool = True + + while (flag): + b: ctypes.c_ubyte = ctypes.c_ubyte(struct.unpack(' 0) + num |= ((b & 127) << num2) + num2 += 7 + if not flag: + break + + return num + +def read7bitEncoded_int(data: BytesIO) -> ctypes.c_int: + num: ctypes.c_int = ctypes.c_int(0) + num2:ctypes.c_int = ctypes.c_int(0) + while (num2 != 35): + b: ctypes.c_ubyte = ctypes.c_ubyte(struct.unpack(' None: + while integer > ctypes.c_ulong(0): + b: ctypes.c_ubyte = ctypes.c_ubyte(integer & ctypes.c_ulong(127)) + integer >>= 7 + if integer > ctypes.c_ulong(0): + b |= 128 + data.write(b) + if integer <= ctypes.c_ulong(0): + return + +def write7bitEncoded_int(data: BytesIO, value: ctypes.c_int) -> None: + num: ctypes.c_uint = ctypes.c_uint(value) + while(num >= ctypes.c_ubyte(128)): + data.write(ctypes.c_ubyte(num | ctypes.c_ubyte(128))) + num >>= 7 + data.Write(ctypes.c_ubyte(num)) + + +#encoding FrooxEngine/C# types in binary: + + + + + diff --git a/core/export_resonite.py b/core/export_resonite.py deleted file mode 100644 index c5a668f..0000000 --- a/core/export_resonite.py +++ /dev/null @@ -1,36 +0,0 @@ -import bpy - -from typing import List, Optional -from .common import get_armature -from bpy.types import Object, ShapeKey, Mesh, Context, Operator -from functools import lru_cache -from ..core.register import register_wrap -from ..functions.translations import t - - -@register_wrap -class AvatarToolKit_OT_ExportResonite(Operator): - bl_idname = 'avatar_toolkit.export_resonite' - bl_label = t("Importer.export_resonite.label") - bl_description = t("Importer.export_resonite.desc") - bl_options = {'REGISTER', 'UNDO'} - filepath: bpy.props.StringProperty() - - - @classmethod - def poll(cls, context: Context): - if get_armature(context) is None: - return False - return True - - def execute(self, context: Context): - #settings stolen from cats. - bpy.ops.export_scene.gltf('INVOKE_AREA', - export_image_format = 'WEBP', - export_image_quality = 75, - export_materials = 'EXPORT', - export_animations = True, - export_animation_mode = 'ACTIONS', - export_nla_strips_merged_animation_name = 'Animation', - export_nla_strips = True) - return {'FINISHED'} \ No newline at end of file diff --git a/core/importer.py b/core/importer.py index 976c7dd..76f9b59 100644 --- a/core/importer.py +++ b/core/importer.py @@ -43,6 +43,7 @@ import_types: dict[str, typing.Callable[[str, list[dict[str,str]], str], None]] "vrm": (lambda directory, files, filepath: bpy.ops.import_scene.vrm(filepath=filepath)), "pmx": (lambda directory, files, filepath : import_pmx(filepath)), "pmd": (lambda directory, files, filepath : import_pmd(filepath)), + "animx": (lambda directory, files, filepath : bpy.ops.avatar_toolkit.animx_importer(directory=directory,files=files,filepath=filepath)), } def concat_imports_filter(imports): diff --git a/core/preferences.json b/core/preferences.json index b0ca7bf..4fd9b5f 100644 --- a/core/preferences.json +++ b/core/preferences.json @@ -1,3 +1,4 @@ { - "language": 0 + "language": 0, + "last_update_check": 1734208835.8049936 } \ No newline at end of file diff --git a/core/resonite_loader/common.py b/core/resonite_loader/common.py new file mode 100644 index 0000000..5c5a943 --- /dev/null +++ b/core/resonite_loader/common.py @@ -0,0 +1,56 @@ +import ctypes +import typing +import struct +from io import BytesIO + +def ReadCSharp_str(data: BytesIO) -> str: + charamount = read7bitEncoded_int(data) + print(charamount) + return data.read(charamount).decode('utf-8', errors="replace") + +def WriteCSharp_str(data: BytesIO, string: str) -> str: + write7bitEncoded_int(len(string)) + return data.write(string.encode("utf-8", errors="replace")) + +def read7bitEncoded_ulong(data: BytesIO) -> int: + num: int = int(0) + num2: int = 0 + flag: bool = True + + while (flag): + b: int = int(struct.unpack(' 0) + num |= ((b & 127) << num2) + num2 += 7 + if not flag: + break + + return num + +def read7bitEncoded_int(data: BytesIO) -> int: + num: int= int(0) + num2:int = int(0) + while (num2 != 35): + b: int = int(struct.unpack(' None: + while integer > int(0): + b: int = ctypes.c_ubyte(integer & int(127)) + integer >>= 7 + if integer > int(0): + b |= 128 + data.write(b) + if integer <= int(0): + return + +def write7bitEncoded_int(data: BytesIO, value: int) -> None: + num: int = int(value) + while(num >= int(128)): + data.write(int(num | int(128))) + num >>= 7 + data.Write(int(num)) \ No newline at end of file diff --git a/core/resonite_loader/resonite_animx.py b/core/resonite_loader/resonite_animx.py new file mode 100644 index 0000000..4c84e41 --- /dev/null +++ b/core/resonite_loader/resonite_animx.py @@ -0,0 +1,472 @@ +from __future__ import annotations + +import lz4.block +from . import resonite_types +from . import common + +import ctypes +import typing +import struct +from io import BytesIO + +KeyframeInterpolation: dict[str, int] = { + "Hold": 1, + "Linear": 2, + "Tangent": 3, + "CubicBezier": 4 +} + +class KeyFrame(): + time: resonite_types.float = 0 + interpolation: resonite_types.int = 0 + value: resonite_types.ResoType + left_tan: resonite_types.ResoType + right_tan: resonite_types.ResoType + + + def __getattr__(self, name: str): + if name == "interpolation": + interp: int = 0 + if (self.__dict__["left_tan"] != None and self.__dict__["right_tan"] != None): + interp = 3 + + + return resonite_types.int(interp) + return self.__dict__[name] + def __setattr__(self, name, value): + self.__dict__[name] = value + + + + + def __init__(self): + pass + + + def RequiresTangents(self) -> bool: + if KeyframeInterpolation[self.interpolation.x] == "Tangent" or KeyframeInterpolation[self.interpolation.x] == "CubicBezier": + return True + return False + +class ResoTrack(resonite_types.ResoType): + _node: resonite_types.string + _property: resonite_types.string + Owner: AnimX + FrameType: type[resonite_types.ResoType] + keyframes: list[KeyFrame] = [] + + def __init__(self,FrameType): + self.FrameType = FrameType + + def write(self, data: BytesIO): + self._node.write(data) + self._property.write(data) + common.write7bitEncoded_ulong(data, len(self.keyframes)) + + def read(self, data:BytesIO): + self._node.read(data) + self._property.read(data) + track_amount: int = int(common.read7bitEncoded_ulong(data)) + for i in range(0, track_amount): + self.keyframes.append(KeyFrame()) + + def removeKeyframe(self, time: float | int) -> bool: + """Takes a time and removes one with the same time""" + if (time < 0): + raise IndexError("Keyframe time cannot be lower than 0. Value: " + str(time)) + if(type(time) == float): + num: int = -1 + for num2 in range(0,len(self.keyframes)): + if (self.keyframes[num2].time.x == float(time)): + num = num2 + + if num == -1: + return False + + self.keyframes.remove(self.keyframes[num]) + return True + else: + if (int(time) >= len(self.keyframes)): + raise IndexError("Keyframe time cannot be bigger than the amount of keyframes. Value: " + str(time)) + self.keyframes.remove(self.keyframes[int(time)]) + + + + + def replaceKeyframe(self, keyframe: KeyFrame) -> bool: + """Takes a keyframe and replaces one with the same time""" + if (keyframe.time.x < 0): + raise IndexError("Keyframe time cannot be lower than 0. Value: " + str(keyframe.time.x)) + + + num: int = 0 + + if (keyframe.time.x == self.keyframes[self.GetKeyframeIndex(keyframe.time.x)].time.x): + num = len(self.keyframes) + else: + return False + + self.keyframes[num] = keyframe + return True + + def addKeyframe(self, keyframe: KeyFrame) -> int: + if (keyframe.time.x < 0): + raise IndexError("Keyframe time cannot be lower than 0. Value: " + str(keyframe.time.x)) + num: int + if (len(self.keyframes) == 0): + num = 0 + elif (keyframe.time.x >= self.keyframes[-1].time.x): + num = len(self.keyframes) + else: + num = self.GetKeyframeIndex(keyframe.time.x) + 1 + + self.keyframes.insert(num, keyframe) + + return num + + def GetKeyframeIndex(self, time:float | int)-> int: + if(type(time) == float): + if (len(self.keyframes) > 0): + num: int = 0 + if (self.keyframes[-1].time < float(time)): + num = len(self.keyframes) + + while (num < len(self.keyframes) and self.keyframes[num].time < time): + num += 1 + + return num - 1 + + return -1 + else: + return int(time) + + +class RawTrack(ResoTrack): + interval: resonite_types.float = 0 + + def __getattr__(self, name: str): + if name == "interval": + return self.Owner.interval.x + return self.__dict__[name] + + def __init__(self, FrameType): + super().__init__(FrameType) + + def write(self, data: BytesIO): + super().write(data) + self.interval.write(data) + for key in self.keyframes: + key.value.write(data) + + + def read(self, data:BytesIO): + super().read(data) + self.interval.read(data) + for key in self.keyframes: + key.value.read(data) + + def addKeyframe(self, keyframe: KeyFrame) -> int: + num: int = super().addKeyframe(keyframe) + for i in range(0,len(self.keyframes)): + self.keyframes[i].time = i + return num + def removeKeyframe(self, time: float | int) -> bool: + success: bool = super().removeKeyframe(int(time)) + for i in range(0,len(self.keyframes)): + self.keyframes[i].time = i + return success + + + + +class DiscreteTrack(ResoTrack): + + def __init__(self, FrameType): + super().__init__(FrameType) + + def write(self, data: BytesIO): + super().write(data) + + + def read(self, data:BytesIO): + super().read(data) + + def addKeyframe(self, keyframe: KeyFrame) -> int: + num: int = super().addKeyframe(keyframe) + return num + def removeKeyframe(self, time: float | int) -> bool: + success: bool = super().removeKeyframe(time) + return success + + + + +class CurveTrack(ResoTrack): + interpolations: bool = False + tangents: bool = False + sharedinterpolation: resonite_types.int = -1 + + def __getattr__(self, name: str): + if name == "interpolations": + for key in self.keyframes: + if key.interpolation.x != self.sharedinterpolation.x: + return True + elif name == "tangents": + for key in self.keyframes: + if key.interpolation.x == 3 or key.interpolation.x == 4: + return True + return self.__dict__[name] + + def __init__(self, FrameType): + super().__init__(FrameType) + + def write(self, data: BytesIO): + super().write(data) + resonite_types.byte((1 if self.interpolations else 0) | (2 if self.tangents else 0)).write(data) + + + if(self.interpolations): + for key in self.keyframes: + key.interpolation.write(data) + else: + self.sharedinterpolation.write(data) + + for key in self.keyframes: + key.value.write(data) + key.time.write(data) + + if(self.tangents): + for key in self.keyframes: + key.left_tan.write(data) + key.right_tan.write(data) + + def read(self, data:BytesIO): + super().read(data) + flags: int = struct.unpack(" bool: + """PLACE HOLDER METHOD, DO NOT USE""" + raise Exception("BezierTrack track type is unsupported in resonite's code") + + def replaceKeyframe(self, keyframe: KeyFrame) -> bool: + """PLACE HOLDER METHOD, DO NOT USE""" + raise Exception("BezierTrack track type is unsupported in resonite's code") + def addKeyframe(self, keyframe: KeyFrame) -> int: + """PLACE HOLDER METHOD, DO NOT USE""" + raise Exception("BezierTrack track type is unsupported in resonite's code") + + def GetKeyframeIndex(self, time:float)-> int: + """PLACE HOLDER METHOD, DO NOT USE""" + raise Exception("BezierTrack track type is unsupported in resonite's code") +#This is weird, but thank you python - @989onan +TrackTypes: list[type[ResoTrack]] = [ + RawTrack, + DiscreteTrack, + CurveTrack, + BezierTrack +] + +#TODO: add all types here +#wooooo - @989onan +elementTypes: list[type[resonite_types.ResoType]] = [ + resonite_types.bool, + resonite_types.bool2, + resonite_types.bool3, + resonite_types.bool4, + resonite_types.byte, + resonite_types.ushort, + resonite_types.uint, + resonite_types.ulong, + resonite_types.sbyte, + resonite_types.short, + resonite_types.int, + resonite_types.long, + resonite_types.int2, + resonite_types.int3, + resonite_types.int4, + resonite_types.uint2, + resonite_types.uint3, + resonite_types.uint4, + resonite_types.long2, + resonite_types.long3, + resonite_types.long4, + resonite_types.float, + resonite_types.float2, + resonite_types.float3, + resonite_types.float4, + resonite_types.floatQ, + resonite_types.float2x2, + resonite_types.float3x3, + resonite_types.float4x4, + resonite_types.double, + resonite_types.double2, + resonite_types.double3, + resonite_types.double4, + resonite_types.doubleQ, + resonite_types.double2x2, + resonite_types.double3x3, + resonite_types.double4x4, + resonite_types.color, + resonite_types.color32, + resonite_types.string + ] + + +most_recent_AnimX_vers: int = 1 + +class AnimX(): + + + """ + To use Raw Track properly, please set interval (seconds between frames) after reading/creating.\n + Represents data to be written to or read from an AnimX file. + """ + + file_version: resonite_types.int = resonite_types.int() + track_amount: resonite_types.int = resonite_types.int() + global_duration: resonite_types.float = resonite_types.float() + name: resonite_types.string = resonite_types.string() + + tracks: list[ResoTrack] = [] + + interval: resonite_types.float = resonite_types.float(1/25) #default value + + def __init__(self): + pass + + + + def read(self, file: str) -> bool: + """ + Takes an absolute file path and reads a binary animx file with it, and populates this class object with the data. + """ + with open(file, 'rb') as filecontents: + data: BytesIO = BytesIO(filecontents.read()) + magic_word = common.ReadCSharp_str(data) + if magic_word != 'AnimX': + print("AnimX != "+magic_word) + return False + self.file_version.read(data) + if self.file_version.x > 1: + raise Exception("AnimX version is higher than the supported one") + + self.track_amount.x = common.read7bitEncoded_ulong(data) + self.global_duration.read(data) + print(self.track_amount.x) + + self.name.read(data) + + + match (struct.unpack('> 1 + else: + trackType2 = int(struct.unpack(' bool: + """ + Takes an absolute file path and writes a binary animx file into it's contents, replacing them using this class's data. + """ + with open(file, 'rb') as filecontents: + data: BytesIO = BytesIO(filecontents) + common.WriteCSharp_str(data, 'AnimX') + self.file_version.x = most_recent_AnimX_vers #we wanna write an up to date file version type. + self.file_version.write(data) + + self.track_amount.x = len(self.tracks) + common.write7bitEncoded_ulong(self.track_amount.x) + self.global_duration.write(data) + + + self.name.write(data) + + data.write(struct.pack(' ResoTrack: + TrackType: type[ResoTrack] = TrackTypes[trackType2] + Track: ResoTrack = TrackType[elementTypes[value_type]](elementTypes[value_type]) + Track.read(data) + return Track + diff --git a/core/resonite_loader/resonite_types.py b/core/resonite_loader/resonite_types.py new file mode 100644 index 0000000..a5c3abe --- /dev/null +++ b/core/resonite_loader/resonite_types.py @@ -0,0 +1,655 @@ +import ctypes +import typing +from io import BytesIO +import struct +from . import common + +class ResoType(): + + + + + def __init__(self): + pass + + def write(self, data: BytesIO): + pass + + def read(cls, data: BytesIO): + pass + +#These below are collection of the basic resonite typing made from C#. This is in order to store data in a sane way and decode/encode it. + +class color(ResoType): + r: float = 0 + g: float = 0 + b: float = 0 + a: float = 0 + + + def __init__(self): + pass + + def write(self, data: BytesIO): + data.write(struct.pack(" str: + return self.x + + def __init__(self,value=""): + self.x = value + + def write(self, data: BytesIO): + common.WriteCSharp_str(data,self.x) + + def read(self,data): + + self.x = common.ReadCSharp_str(data) + + +class byte(ResoType): + x: int = 0 + + def __int__(self): + return self.x + + def __init__(self): + pass + + def write(self, data: BytesIO): + data.write(struct.pack(" bool: + return self.x + def __init__(self,value=False): + self.x = value + + def write(self, data: BytesIO): + data.write(struct.pack("?", self.x)) + + def read(self,data): + self.x = struct.unpack("?", data.read(1))[0] + +class bool2(bool): + y: bool = False + + def __init__(self): + pass + + def read(self,data: BytesIO): + byte: ctypes.c_ubyte = ctypes.c_ubyte(struct.unpack(" 0 + self.y = (byte & 2) > 0 + + def createflags(self) -> ctypes.c_byte: + flags: ctypes.c_ubyte = ctypes.c_ubyte(0) + flags |= (1 if self.x else 0) + flags |= (2 if self.y else 0) + return flags + + def write(self, data: BytesIO): + data.write(struct.pack(" 0 + self.y = (byte & 2) > 0 + self.z = (byte & 4) > 0 + + def createflags(self) -> ctypes.c_byte: + flags: ctypes.c_ubyte = ctypes.c_ubyte(0) + flags |= (1 if self.x else 0) + flags |= (2 if self.y else 0) + flags |= (3 if self.z else 0) + return flags + + def write(self, data: BytesIO): + data.write(struct.pack(" 0 + self.y = (byte & 2) > 0 + self.z = (byte & 4) > 0 + self.w = (byte & 8) > 0 + + def createflags(self) -> ctypes.c_ubyte: + flags: ctypes.c_ubyte = ctypes.c_ubyte(0) + flags |= (1 if self.x else 0) + flags |= (2 if self.y else 0) + flags |= (4 if self.z else 0) + flags |= (8 if self.w else 0) + return flags + + def write(self, data: BytesIO): + data.write(struct.pack(" bool: + return True + + def execute(self, context: Context) -> set: + + Froox_animations: list[resonite_animx.AnimX] = [] + + #decoding using self contained library: + files = [file.name for file in self.files] + files.append(self.filepath) + for file in files: + froox_animation: resonite_animx.AnimX = resonite_animx.AnimX() + froox_animation.interval.x = 1/25 + froox_animation.read(file = os.path.join(self.directory,file)) + Froox_animations.append(froox_animation) + + #Load data into Blender Animations. + for froox_animation in Froox_animations: + action: bpy.types.Action = bpy.data.actions.new(froox_animation.name.x) + action.use_fake_user = True + for track in froox_animation.tracks: + print("hit here1") + print(track.FrameType) + if(track.FrameType != type(resonite_types.float) and track.FrameType != type(resonite_types.double)): + continue + + data_path: str = track._node.x+"."+track._property.x + fcurve_reso = action.fcurves.new(data_path,None,track._node.x) + + print("hit here2") + match(type(track)): + case (type(resonite_animx.RawTrack)): + rawtrack: resonite_animx.RawTrack = track + + + + for frame in rawtrack.keyframes: + key: bpy.types.Keyframe = fcurve_reso.keyframe_points.insert(frame.time, float(frame.value)) + + + case (type(resonite_animx.DiscreteTrack)): + discretetrack: resonite_animx.RawTrack = track + for frame in rawtrack.keyframes: + key: bpy.types.Keyframe = fcurve_reso.keyframe_points.insert(frame.time, float(frame.value)) + + + case(type(resonite_animx.CurveTrack)): + curvetrack: resonite_animx.RawTrack = track + for frame in curvetrack.keyframes: + key: bpy.types.Keyframe = fcurve_reso.keyframe_points.insert(frame.time, float(frame.value)) + key.handle_left = float(frame.left_tan) + key.handle_left = float(frame.right_tan) + + + case(type(resonite_animx.BezierTrack)): + beziertrack: resonite_animx.RawTrack = track + # Bezier is not supported rn, ignore. + + + + + + + + return {'FINISHED'} + + + + + + + + diff --git a/functions/resonite_functions.py b/functions/resonite_functions.py index 1f32e2e..e69de29 100644 --- a/functions/resonite_functions.py +++ b/functions/resonite_functions.py @@ -1,115 +0,0 @@ -import bpy -from ..core.register import register_wrap -from typing import List, Optional -import re -from bpy.types import Operator, Context, Object -from ..core.dictionaries import bone_names -from ..core.common import get_selected_armature, simplify_bonename, is_valid_armature -from ..functions.translations import t - -@register_wrap -class AvatarToolKit_OT_ConvertToResonite(Operator): - bl_idname = 'avatar_toolkit.convert_to_resonite' - bl_label = t('Tools.convert_to_resonite.label') - bl_description = t('Tools.convert_to_resonite.desc') - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context: Context) -> bool: - armature = get_selected_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: - self.report({'WARNING'}, t("Tools.no_armature_selected")) - return {'CANCELLED'} - - translate_bone_fails = 0 - untranslated_bones = set() - - reverse_bone_lookup = dict() - for (preferred_name, name_list) in bone_names.items(): - for name in name_list: - reverse_bone_lookup[name] = preferred_name - - resonite_translations = { - 'hips': "Hips", - 'spine': "Spine", - 'chest': "Chest", - '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", - 'pinkie_2_l': "pinkie2.L", - 'pinkie_3_l': "pinkie3.L", - '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", - 'pinkie_2_r': "pinkie2.R", - 'pinkie_3_r': "pinkie3.R", - 'ring_1_r': "ring1.R", - 'ring_2_r': "ring2.R", - 'ring_3_r': "ring3.R", - '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 - bpy.ops.object.mode_set(mode='EDIT') - - bpy.ops.object.mode_set(mode='OBJECT') - bone.name = re.compile(re.escape(""), 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+"" - translate_bone_fails += 1 - - bpy.ops.object.mode_set(mode='OBJECT') - - if translate_bone_fails > 0: - self.report({'INFO'}, t("Tools.bones_translated_with_fails").format(translate_bone_fails=translate_bone_fails)) - else: - self.report({'INFO'}, t("Tools.bones_translated_success")) - - return {'FINISHED'} diff --git a/functions/uv_tools.py b/functions/uv_tools.py index 504fc84..66e6aae 100644 --- a/functions/uv_tools.py +++ b/functions/uv_tools.py @@ -287,6 +287,9 @@ class AvatarToolkit_OT_AlignUVEdgesToTarget(Operator): uv_lay.uv[corner].vector = lerped_point #put the vertcies at the point we calculated. except: print("This is probably fine? - @989onan") #TODO: What happened here? The magic of making code so complex you forget if this is even an issue. - @989onan + #Note: The above print statement may require reading what this entire operator does before understanding if the above print statment means anything + #so if you value your time, just do a bunch of tests until the above print stament is ran or something + #if in doubt and you cannot get it to run, just spend some time trying to understand the operator - @989onan print("Finished mesh \""+source+"\" for UV's")