Vendored
+1
@@ -3,6 +3,7 @@
|
|||||||
"D:\\SteamLibrary\\steamapps\\common\\Blender\\4.3\\scripts\\addons",
|
"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
|
"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:\\blender stuff\\blendercodestuff\\4.3",
|
||||||
|
"D:\\SteamLibrary\\steamapps\\common\\Blender\\4.3\\python\\lib\\site-packages",
|
||||||
"/Users/frankche/Documents/blendercoding/4.1/",
|
"/Users/frankche/Documents/blendercoding/4.1/",
|
||||||
"/Users/frankche/Library/Application Support/Blender/4.3/extensions/user_default/"
|
"/Users/frankche/Library/Application Support/Blender/4.3/extensions/user_default/"
|
||||||
],
|
],
|
||||||
|
|||||||
+24
-1
@@ -1,8 +1,18 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import webbrowser
|
||||||
|
import typing
|
||||||
|
import struct
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from typing import Optional, Tuple, List, Set, Dict, Any, Generator, Callable
|
||||||
from mathutils import Vector
|
from mathutils import Vector
|
||||||
from bpy.types import Context, Object, Modifier, EditBone, Operator
|
from bpy.types import Context, Object, Modifier, EditBone, Operator
|
||||||
from typing import Optional, Tuple, List, Set, Dict, Any, Generator, Callable
|
from functools import lru_cache
|
||||||
|
from bpy.props import PointerProperty, IntProperty, StringProperty
|
||||||
|
from bpy.utils import register_class
|
||||||
from ..core.logging_setup import logger
|
from ..core.logging_setup import logger
|
||||||
from ..core.translations import t
|
from ..core.translations import t
|
||||||
from ..core.dictionaries import bone_names
|
from ..core.dictionaries import bone_names
|
||||||
@@ -616,3 +626,16 @@ def add_armature_modifier(mesh: Object, armature: Object):
|
|||||||
|
|
||||||
modifier = mesh.modifiers.new('Armature', 'ARMATURE')
|
modifier = mesh.modifiers.new('Armature', 'ARMATURE')
|
||||||
modifier.object = armature
|
modifier.object = armature
|
||||||
|
|
||||||
|
#Binary tools
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#encoding FrooxEngine/C# types in binary:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
import bpy
|
|
||||||
from typing import List, Optional
|
|
||||||
from ...core.common import get_active_armature
|
|
||||||
from bpy.types import Object, ShapeKey, Mesh, Context, Operator
|
|
||||||
from functools import lru_cache
|
|
||||||
from ...core.translations import t
|
|
||||||
|
|
||||||
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_active_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'}
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import bpy
|
||||||
|
|
||||||
|
# Importers which don't need much code should be added here, however if a importer needs alot of code
|
||||||
|
# Like the PMX and PMD importers, they should be added to their own files and referenced in the import_types str->lambda dictionary.
|
||||||
|
|
||||||
|
#See below comments on how the system works. - @989onan
|
||||||
|
|
||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
import typing
|
||||||
|
from .import_pmx import import_pmx
|
||||||
|
from .import_pmd import import_pmd
|
||||||
|
|
||||||
|
if importlib.util.find_spec("io_scene_valvesource") is not None:
|
||||||
|
#from .....scripts.addons.io_scene_valvesource.import_smd import SmdImporter #<- use this to check if your IDE is working properly. idfk
|
||||||
|
from io_scene_valvesource.import_smd import SmdImporter #ignore IDE bitching this is fine, trust me, also above comment should be okay to an IDE usually if set up right. ^_^ - @989onan
|
||||||
|
|
||||||
|
def import_multi_files(method = None, directory: typing.Optional[str] = None, files: list[dict[str,str]] = None, filepath: typing.Optional[str] = ""):
|
||||||
|
if not files:
|
||||||
|
method(directory, filepath)
|
||||||
|
else:
|
||||||
|
for file in files:
|
||||||
|
fullpath = os.path.join(directory,os.path.basename(file["name"]))
|
||||||
|
print("run method!")
|
||||||
|
method(directory, fullpath)
|
||||||
|
#each import should map to a type. even in the case that multiple methods should import together, or have the same import method. Make sure the lambdas match so they get grouped together
|
||||||
|
#In the case of a file importer that takes only one file argument and each one needs individual import, use above method. (example of it in use is ".dae" format)
|
||||||
|
import_types: dict[str, typing.Callable[[str, list[dict[str,str]], str], None]] = {
|
||||||
|
"fbx": (lambda directory, files, filepath : bpy.ops.import_scene.fbx(files=files, directory=directory, filepath=filepath,automatic_bone_orientation=False,use_prepost_rot=False,use_anim=False)),
|
||||||
|
"smd": (lambda directory, files, filepath : eval("bpy."+SmdImporter.bl_idname+".(files=files, directory=directory, filepath=filepath)")),
|
||||||
|
"dmx": (lambda directory, files, filepath: eval("bpy."+SmdImporter.bl_idname+".(files=files, directory=directory, filepath=filepath)")),
|
||||||
|
"gltf": (lambda directory, files, filepath : bpy.ops.import_scene.gltf(files=files, filepath=filepath)),
|
||||||
|
"glb": (lambda directory, files, filepath : bpy.ops.import_scene.gltf(files=files, filepath=filepath)),
|
||||||
|
"qc": (lambda directory, files, filepath : eval("bpy."+SmdImporter.bl_idname+".(files=files, directory=directory, filepath=filepath)")),
|
||||||
|
"obj": (lambda directory, files, filepath : bpy.ops.wm.obj_import(files=files, directory=directory, filepath=filepath)),
|
||||||
|
"dae": (lambda directory, files, filepath : import_multi_files(directory=directory, files=files, filepath=filepath, method = (lambda directory, filepath: bpy.ops.wm.collada_import(filepath=filepath, auto_connect = True, find_chains = True, fix_orientation = True)))),
|
||||||
|
"3ds": (lambda directory, files, filepath : bpy.ops.import_scene.max3ds(files=files, directory=directory, filepath=filepath)),
|
||||||
|
"stl": (lambda directory, files, filepath : bpy.ops.import_mesh.stl(files=files, directory=directory, filepath=filepath)),
|
||||||
|
"mtl": (lambda directory, files, filepath : bpy.ops.wm.obj_import(files=files, directory=directory, filepath=filepath)),
|
||||||
|
"x3d": (lambda directory, files, filepath : bpy.ops.import_scene.x3d(files=files, directory=directory, filepath=filepath)),
|
||||||
|
"wrl": (lambda directory, files, filepath : bpy.ops.import_scene.x3d(files=files, directory=directory, filepath=filepath)),
|
||||||
|
"vmd": (lambda directory, files, filepath : import_multi_files(directory=directory, files=files, filepath=filepath, method = (lambda directory, filepath: bpy.ops.tuxedo.import_mmd_animation(directory=directory, filepath=filepath)))),
|
||||||
|
"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):
|
||||||
|
names = ""
|
||||||
|
for importer in imports.keys():
|
||||||
|
names = names+"*."+importer+";"
|
||||||
|
return names
|
||||||
|
|
||||||
|
imports = concat_imports_filter(import_types)
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import ctypes
|
||||||
|
import typing
|
||||||
|
import struct
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
def writeNullable(data: BytesIO, value: = None):
|
||||||
|
|
||||||
|
data.write(struct.pack("?", value == None))
|
||||||
|
if(value == None):
|
||||||
|
return
|
||||||
|
data.write()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def ReadCSharp_str(data: BytesIO) -> str:
|
||||||
|
charamount = read7bitEncoded_int(data)
|
||||||
|
string: str = data.read(charamount).decode('utf-8', errors="replace")
|
||||||
|
#print("read string: "+string)
|
||||||
|
return string
|
||||||
|
|
||||||
|
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('<B', data.read(1))[0])
|
||||||
|
flag = ((b & 128) > 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('<B', data.read(1))[0])
|
||||||
|
num |= int(b & 127) << num2
|
||||||
|
num2 += 7
|
||||||
|
if ((b & 128) == 0):
|
||||||
|
return num
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def write7bitEncoded_ulong(data: BytesIO, integer: int) -> None:
|
||||||
|
while integer > int(0):
|
||||||
|
b: int = int(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))
|
||||||
@@ -0,0 +1,568 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from os import replace
|
||||||
|
from re import S
|
||||||
|
from types import FrameType
|
||||||
|
|
||||||
|
import lz4.block
|
||||||
|
from . import resonite_types
|
||||||
|
from . import common
|
||||||
|
|
||||||
|
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
|
||||||
|
interpolation: resonite_types.byte
|
||||||
|
value: resonite_types.ResoType
|
||||||
|
left_tan: resonite_types.ResoType
|
||||||
|
right_tan: resonite_types.ResoType
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.time = resonite_types.float(0)
|
||||||
|
self.interpolation = resonite_types.byte(0)
|
||||||
|
|
||||||
|
|
||||||
|
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: str
|
||||||
|
keyframes: list[KeyFrame]
|
||||||
|
|
||||||
|
def __init__(self,FrameType):
|
||||||
|
self.FrameType = FrameType
|
||||||
|
self.keyframes = []
|
||||||
|
self.node = resonite_types.string("")
|
||||||
|
self.property = resonite_types.string("")
|
||||||
|
|
||||||
|
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))
|
||||||
|
#print(track_amount)
|
||||||
|
for i in range(0, track_amount):
|
||||||
|
key: KeyFrame = KeyFrame()
|
||||||
|
key.value = eval(self.FrameType+"()")
|
||||||
|
self.keyframes.append(key)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def __getattr__(self, name: str):
|
||||||
|
if name == "interval":
|
||||||
|
return self.Owner.interval
|
||||||
|
return super().__getattribute__(name)
|
||||||
|
|
||||||
|
def __init__(self, FrameType):
|
||||||
|
super().__init__(FrameType)
|
||||||
|
self.interval = resonite_types.float(0)
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
self.interval.write(data)
|
||||||
|
for key in self.keyframes:
|
||||||
|
if self.FrameType == "resonite_types.string":
|
||||||
|
resonite_types.writeNullable(data, key.value)
|
||||||
|
else:
|
||||||
|
key.value.write(data)
|
||||||
|
|
||||||
|
|
||||||
|
def read(self, data:BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.interval.read(data)
|
||||||
|
for key in self.keyframes:
|
||||||
|
if self.FrameType == "resonite_types.string":
|
||||||
|
resonite_types.readNullable(data, key.value)
|
||||||
|
else:
|
||||||
|
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)
|
||||||
|
self.interval.write(data)
|
||||||
|
for key in self.keyframes:
|
||||||
|
if key.value == None:
|
||||||
|
key.value = eval(self.FrameType+"()")
|
||||||
|
if self.FrameType == "resonite_types.string":
|
||||||
|
resonite_types.writeNullable(data, key.value)
|
||||||
|
else:
|
||||||
|
key.value.write(data)
|
||||||
|
key.time.write(data)
|
||||||
|
|
||||||
|
|
||||||
|
def read(self, data:BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.interval.read(data)
|
||||||
|
for key in self.keyframes:
|
||||||
|
if key.value == None:
|
||||||
|
key.value = eval(self.FrameType+"()")
|
||||||
|
if self.FrameType == "resonite_types.string":
|
||||||
|
resonite_types.readNullable(data, key.value)
|
||||||
|
else:
|
||||||
|
key.value.read(data)
|
||||||
|
key.time.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
|
||||||
|
tangents: bool
|
||||||
|
sharedinterpolation: resonite_types.byte
|
||||||
|
|
||||||
|
def __getattr__(self, name: str):
|
||||||
|
if name == "interpolations":
|
||||||
|
integerframe: int = self.keyframes[0].interpolation.x
|
||||||
|
for key in self.keyframes:
|
||||||
|
if key.interpolation.x != integerframe:
|
||||||
|
return True
|
||||||
|
elif name == "tangents":
|
||||||
|
for key in self.keyframes:
|
||||||
|
if key.RequiresTangents():
|
||||||
|
return True
|
||||||
|
return super().__getattribute__(name)
|
||||||
|
|
||||||
|
def __init__(self, FrameType):
|
||||||
|
super().__init__(FrameType)
|
||||||
|
self.sharedinterpolation = resonite_types.byte(-1)
|
||||||
|
self.interpolations = False
|
||||||
|
self.tangents = False
|
||||||
|
|
||||||
|
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:
|
||||||
|
if key.value == None:
|
||||||
|
key.value = eval(self.FrameType+"()")
|
||||||
|
if self.FrameType == "resonite_types.string":
|
||||||
|
resonite_types.writeNullable(data, key.value)
|
||||||
|
else:
|
||||||
|
key.value.write(data)
|
||||||
|
key.time.write(data)
|
||||||
|
|
||||||
|
if(self.tangents):
|
||||||
|
for key in self.keyframes:
|
||||||
|
if self.FrameType == "resonite_types.string":
|
||||||
|
resonite_types.writeNullable(data, key.left_tan)
|
||||||
|
resonite_types.writeNullable(data, key.right_tan)
|
||||||
|
else:
|
||||||
|
key.left_tan.write(data)
|
||||||
|
key.right_tan.write(data)
|
||||||
|
|
||||||
|
def read(self, data:BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
flags: int = struct.unpack("<B",data.read(1))[0]
|
||||||
|
interp: bool = (flags & 1) > 0
|
||||||
|
tan: bool = (flags & 2) > 0
|
||||||
|
|
||||||
|
#print(str(interp))
|
||||||
|
#print(str(tan))
|
||||||
|
#print(flags)
|
||||||
|
#print(len(self.keyframes))
|
||||||
|
|
||||||
|
if(interp):
|
||||||
|
for key in self.keyframes:
|
||||||
|
#print("reading interp")
|
||||||
|
key.interpolation.read(data)
|
||||||
|
else:
|
||||||
|
self.sharedinterpolation.read(data)
|
||||||
|
|
||||||
|
for key in self.keyframes:
|
||||||
|
if key.value == None:
|
||||||
|
key.value = eval(self.FrameType+"()")
|
||||||
|
if self.FrameType == "resonite_types.string":
|
||||||
|
resonite_types.readNullable(data, key.value)
|
||||||
|
else:
|
||||||
|
key.value.read(data)
|
||||||
|
key.time.read(data)
|
||||||
|
|
||||||
|
if(tan):
|
||||||
|
for key in self.keyframes:
|
||||||
|
if self.FrameType == "resonite_types.string":
|
||||||
|
resonite_types.readNullable(data, key.left_tan)
|
||||||
|
resonite_types.readNullable(data, key.right_tan)
|
||||||
|
else:
|
||||||
|
key.left_tan.read(data)
|
||||||
|
key.right_tan.read(data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BezierTrack(ResoTrack):
|
||||||
|
"""PLACE HOLDER CLASS, DO NOT USE"""
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, FrameType):
|
||||||
|
super().__init__(FrameType)
|
||||||
|
"""PLACE HOLDER METHOD, DO NOT USE"""
|
||||||
|
#raise Exception("BezierTrack track type is unsupported in resonite's code")
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
"""PLACE HOLDER METHOD, DO NOT USE"""
|
||||||
|
raise Exception("BezierTrack track type is unsupported in resonite's code")
|
||||||
|
def read(self, data:BytesIO):
|
||||||
|
"""PLACE HOLDER METHOD, DO NOT USE"""
|
||||||
|
raise Exception("BezierTrack track type is unsupported in resonite's code")
|
||||||
|
|
||||||
|
def removeKeyframe(self, keyframe: KeyFrame) -> 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[str] = [
|
||||||
|
"RawTrack",
|
||||||
|
"DiscreteTrack",
|
||||||
|
"CurveTrack",
|
||||||
|
"BezierTrack"
|
||||||
|
]
|
||||||
|
|
||||||
|
#TODO: add all types here
|
||||||
|
#wooooo - @989onan
|
||||||
|
elementTypes: list[str] = [
|
||||||
|
"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
|
||||||
|
import lzma#HALLLOOOYAH HALLOYAH!! - @989onan
|
||||||
|
class AnimX():
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
To use Raw Track properly, please set interval (seconds between frames) after creating.\n
|
||||||
|
Represents data to be written to or read from an AnimX file.\n
|
||||||
|
default interval to use would be 30.
|
||||||
|
"""
|
||||||
|
|
||||||
|
file_version: resonite_types.int
|
||||||
|
track_amount: resonite_types.int
|
||||||
|
global_duration: resonite_types.float
|
||||||
|
name: resonite_types.string
|
||||||
|
|
||||||
|
tracks: list[ResoTrack]
|
||||||
|
|
||||||
|
interval: resonite_types.float
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.tracks = []
|
||||||
|
self.file_version = resonite_types.int()
|
||||||
|
self.track_amount = resonite_types.int()
|
||||||
|
self.global_duration = resonite_types.float()
|
||||||
|
self.name = resonite_types.string()
|
||||||
|
self.interval = resonite_types.float(1/25) #default value
|
||||||
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def decompress_lzma(cls,data, format, filters) -> list:
|
||||||
|
results = []
|
||||||
|
while True:
|
||||||
|
decomp = lzma.LZMADecompressor(format, None, filters)
|
||||||
|
try:
|
||||||
|
res = decomp.decompress(data)
|
||||||
|
except lzma.LZMAError:
|
||||||
|
if results:
|
||||||
|
break # Leftover data is not a valid LZMA/XZ stream; ignore it.
|
||||||
|
else:
|
||||||
|
raise # Error on the first iteration; bail out.
|
||||||
|
results.append(res)
|
||||||
|
data = decomp.unused_data
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
if not decomp.eof:
|
||||||
|
raise lzma.LZMAError("Compressed data ended before the end-of-stream marker was reached")
|
||||||
|
return b"".join(results)
|
||||||
|
|
||||||
|
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("track amount: "+str(self.track_amount.x))
|
||||||
|
print("file vers: "+str(self.file_version.x))
|
||||||
|
|
||||||
|
self.name.read(data)
|
||||||
|
print("name: "+self.name.x)
|
||||||
|
|
||||||
|
match (struct.unpack('<B', data.read(1))[0]):
|
||||||
|
case 0:
|
||||||
|
pass
|
||||||
|
case 1:
|
||||||
|
from lz4.frame import decompress #why do you have to be a wheel? - @989onan
|
||||||
|
data = BytesIO(decompress(data.read()))
|
||||||
|
case 2:
|
||||||
|
|
||||||
|
filters = [
|
||||||
|
{"id" : lzma.FILTER_LZMA1, #idfk man - @989onan
|
||||||
|
"dict_size" : 2097152,
|
||||||
|
"lc" : 3,
|
||||||
|
"lp" : 0,
|
||||||
|
"pb" : 2, # private static int posStateBits = 2; //<-froox engine derived.
|
||||||
|
"mode" : lzma.MODE_NORMAL,
|
||||||
|
"nice_len" : 32, # private static int numFastBytes = 32; //<-froox engine derived.
|
||||||
|
"mf" : lzma.MF_BT4,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
data.read(5) #fuck off headers - @989onan
|
||||||
|
data.read(8) #fuck off stream headers - @989onan
|
||||||
|
data.read(8) #fuck off stream headers - @989onan
|
||||||
|
filelmza: bytes = bytes(AnimX.decompress_lzma(data.read(), lzma.FORMAT_RAW, filters))
|
||||||
|
#print("binary below:")
|
||||||
|
#print("b'{}'".format(''.join('\\x{:02x}'.format(b) for b in filelmza[:100])))
|
||||||
|
data = BytesIO(filelmza)
|
||||||
|
case _:
|
||||||
|
raise Exception("Invalid encoding")
|
||||||
|
|
||||||
|
for i in range(0,self.track_amount.x):
|
||||||
|
trackType2: int = 0
|
||||||
|
num4: int = 0
|
||||||
|
if (self.file_version == 0):
|
||||||
|
b: int = int(struct.unpack('<B', data.read(1))[0])
|
||||||
|
num3: int = int(b & 1)
|
||||||
|
trackType: int = 0
|
||||||
|
if (num3 != 0):
|
||||||
|
if (num3 != 1):
|
||||||
|
raise Exception("[InvalidDataException]: Invalid track type data: " + str(b))
|
||||||
|
trackType = 2
|
||||||
|
else:
|
||||||
|
trackType = 0
|
||||||
|
trackType2 = trackType
|
||||||
|
num4 = b >> 1
|
||||||
|
else:
|
||||||
|
trackType2 = int(struct.unpack('<B', data.read(1))[0])
|
||||||
|
num4 = int(struct.unpack('<B', data.read(1))[0])
|
||||||
|
try:
|
||||||
|
animationTrack = AnimX.GetTrackType(trackType2, elementTypes[num4], data)
|
||||||
|
animationTrack.Owner = self
|
||||||
|
self.tracks.append(animationTrack)
|
||||||
|
except:
|
||||||
|
raise Exception("[InvalidDataException]: element type exception, beyond range: "+str(num4))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def write(self, file: str) -> 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('<B', 0)) #default encoding, so we don't have to use lzma.
|
||||||
|
|
||||||
|
|
||||||
|
for i in range(0,self.track_amount.x):
|
||||||
|
|
||||||
|
data.write(struct.pack('<B', TrackTypes.index(type(self.tracks[i]))))
|
||||||
|
data.write(struct.pack('<B', elementTypes.index(self.tracks[i].FrameType)))
|
||||||
|
self.tracks[i].write(data)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def GetTrackType(cls, trackType2: int, value_type: str, data: BytesIO) -> ResoTrack:
|
||||||
|
Track: ResoTrack = eval(TrackTypes[trackType2]+"(value_type)")
|
||||||
|
#print(value_type)
|
||||||
|
#print(type(Track))
|
||||||
|
Track.read(data)
|
||||||
|
return Track
|
||||||
|
|
||||||
@@ -0,0 +1,665 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
def writeNullable(data: BytesIO, value: ResoType = None):
|
||||||
|
|
||||||
|
data.write(struct.pack("?", value == None))
|
||||||
|
if(value == None):
|
||||||
|
return
|
||||||
|
value.write(data)
|
||||||
|
|
||||||
|
def readNullable(data: BytesIO, value: ResoType = None):
|
||||||
|
|
||||||
|
hasval: bool = struct.unpack("?", data.read(1))
|
||||||
|
if not hasval:
|
||||||
|
return
|
||||||
|
value.read(data)
|
||||||
|
#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("<ffff", self.r, self.g, self.b, self.a))
|
||||||
|
|
||||||
|
def read(self,data):
|
||||||
|
self.r, self.g, self.b, self.a = struct.unpack("<ffff", data.read(4*4))[0]
|
||||||
|
|
||||||
|
|
||||||
|
class color32(ResoType):
|
||||||
|
r: int = 0
|
||||||
|
g: int = 0
|
||||||
|
b: int = 0
|
||||||
|
a: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<BBBB", self.r, self.g, self.b, self.a))
|
||||||
|
|
||||||
|
def read(self,data):
|
||||||
|
self.r, self.g, self.b, self.a = struct.unpack("<BBBB", data.read(4))[0]
|
||||||
|
|
||||||
|
class string(ResoType):
|
||||||
|
x: str
|
||||||
|
|
||||||
|
def __str__(self) -> 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,value=0):
|
||||||
|
self.x = value
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<B", self.x))
|
||||||
|
|
||||||
|
def read(self,data):
|
||||||
|
self.x = struct.unpack("<B", data.read(1))[0]
|
||||||
|
|
||||||
|
class sbyte(ResoType):
|
||||||
|
x: int = 0
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __init__(self,value=0):
|
||||||
|
self.x = value
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<b", self.x))
|
||||||
|
|
||||||
|
def read(self,data):
|
||||||
|
self.x = struct.unpack("<b", data.read(1))[0]
|
||||||
|
|
||||||
|
class ushort(ResoType):
|
||||||
|
x: int = 0
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __init__(self,value=0):
|
||||||
|
self.x = value
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<H", self.x))
|
||||||
|
|
||||||
|
def read(self,data):
|
||||||
|
self.x = struct.unpack("<H", data.read(2))[0]
|
||||||
|
|
||||||
|
class short(ResoType):
|
||||||
|
x: int = 0
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __init__(self,value=0):
|
||||||
|
self.x = value
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<h", self.x))
|
||||||
|
|
||||||
|
def read(self,data):
|
||||||
|
self.x = struct.unpack("<h", data.read(2))[0]
|
||||||
|
|
||||||
|
class bool(ResoType):
|
||||||
|
x: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
def __bool__(self) -> 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: int = int(struct.unpack("<B",data.read(1))[0])
|
||||||
|
self.x = (byte & 1) > 0
|
||||||
|
self.y = (byte & 2) > 0
|
||||||
|
|
||||||
|
def createflags(self) -> int:
|
||||||
|
flags: int = int(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("<B", self.createflags()))
|
||||||
|
|
||||||
|
|
||||||
|
class bool3(bool2):
|
||||||
|
z: bool = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data):
|
||||||
|
byte: int = int(struct.unpack("<B",data.read(1))[0])
|
||||||
|
self.x = (byte & 1) > 0
|
||||||
|
self.y = (byte & 2) > 0
|
||||||
|
self.z = (byte & 4) > 0
|
||||||
|
|
||||||
|
def createflags(self) -> int:
|
||||||
|
flags: int = int(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("<B", self.createflags()))
|
||||||
|
|
||||||
|
|
||||||
|
class bool4(bool3):
|
||||||
|
w: bool = False
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
byte: int = int(struct.unpack("<B",data.read(1))[0])
|
||||||
|
self.x = (byte & 1) > 0
|
||||||
|
self.y = (byte & 2) > 0
|
||||||
|
self.z = (byte & 4) > 0
|
||||||
|
self.w = (byte & 8) > 0
|
||||||
|
|
||||||
|
def createflags(self) -> int:
|
||||||
|
flags: int = int(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("<B", self.createflags()))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class int(ResoType):
|
||||||
|
x: int = 0
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __init__(self,value=0):
|
||||||
|
self.x = value
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.x = struct.unpack("<i", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<i", self.x))
|
||||||
|
|
||||||
|
class int2(int):
|
||||||
|
y: int = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
self.y = struct.unpack("<i", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<i", self.y))
|
||||||
|
|
||||||
|
class int3(int2):
|
||||||
|
z: int = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
self.z = struct.unpack("<i", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<i", self.z))
|
||||||
|
|
||||||
|
class int4(int3):
|
||||||
|
w: int = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.w = struct.unpack("<i", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<i", self.w))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class uint(ResoType):
|
||||||
|
x: int = 0
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __init__(self,value=0):
|
||||||
|
self.x = value
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.x = struct.unpack("<I", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<I", self.x))
|
||||||
|
|
||||||
|
class uint2(uint):
|
||||||
|
y: int = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.y = struct.unpack("<I", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<I", self.y))
|
||||||
|
|
||||||
|
class uint3(uint2):
|
||||||
|
z: int = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.z = struct.unpack("<I", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<I", self.z))
|
||||||
|
|
||||||
|
class uint4(uint3):
|
||||||
|
w: int = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.w = struct.unpack("<I", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<I", self.w))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ulong(ResoType):
|
||||||
|
x: int = 0
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __init__(self,value=0):
|
||||||
|
self.x = value
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.x = struct.unpack("<Q", data.read(8))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<Q", self.x))
|
||||||
|
|
||||||
|
|
||||||
|
class long(ResoType):
|
||||||
|
x: int = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.x = struct.unpack("<q", data.read(8))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<q", self.x))
|
||||||
|
|
||||||
|
class long2(long):
|
||||||
|
y: int = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.y = struct.unpack("<q", data.read(8))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<q", self.y))
|
||||||
|
|
||||||
|
class long3(long2):
|
||||||
|
z: int = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.z = struct.unpack("<q", data.read(8))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<q", self.z))
|
||||||
|
|
||||||
|
class long4(long3):
|
||||||
|
w: int = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.w = struct.unpack("<q", data.read(8))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<q", self.w))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class double(ResoType):
|
||||||
|
x: float = 0
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __init__(self,value=0):
|
||||||
|
self.x = value
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.x = struct.unpack("<d", data.read(8))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<d", self.x))
|
||||||
|
|
||||||
|
class double2(double):
|
||||||
|
|
||||||
|
y: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.y = struct.unpack("<d", data.read(8))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<d", self.y))
|
||||||
|
|
||||||
|
class double3(double2):
|
||||||
|
z: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.z = struct.unpack("<d", data.read(8))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<d", self.z))
|
||||||
|
|
||||||
|
class double4(double3):
|
||||||
|
w: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.w = struct.unpack("<d", data.read(8))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<d", self.w))
|
||||||
|
|
||||||
|
class double2x2(ResoType):
|
||||||
|
m00: float = 0
|
||||||
|
m01: float = 0
|
||||||
|
m10: float = 0
|
||||||
|
m11: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.m00,self.m01,self.m10,self.m11 = struct.unpack("<dddd", data.read(8*(2*2)))
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<dddd", self.m00,self.m01, self.m10,self.m11))
|
||||||
|
|
||||||
|
|
||||||
|
class double3x3(double2x2):
|
||||||
|
m02: float = 0
|
||||||
|
m12: float = 0
|
||||||
|
m22: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.m00,self.m01,self.m02,self.m10,self.m11,self.m12,self.m20,self.m21,self.m22 = struct.unpack("<ddddddddd", data.read(8*(3*3)))
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<ddddddddd", self.m00,self.m01,self.m02,self.m10,self.m11,self.m12,self.m20,self.m21,self.m22))
|
||||||
|
|
||||||
|
|
||||||
|
class double4x4(double3x3):
|
||||||
|
m03: float = 0
|
||||||
|
m13: float = 0
|
||||||
|
m23: float = 0
|
||||||
|
m33: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.m00,self.m01,self.m02,self.m03,self.m10,self.m11,self.m12,self.m13,self.m20,self.m21,self.m22,self.m23,self.m30,self.m31,self.m32,self.m33 = struct.unpack("<dddddddddddddddd", data.read(8*(4*4)))
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<dddddddddddddddd", self.m00,self.m01,self.m02,self.m03,self.m10,self.m11,self.m12,self.m13,self.m20,self.m21,self.m22,self.m23,self.m30,self.m31,self.m32,self.m33))
|
||||||
|
|
||||||
|
class doubleQ(double4):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
|
||||||
|
def read(self, data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class float(ResoType):
|
||||||
|
x: float = 0
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return self.x
|
||||||
|
|
||||||
|
def __init__(self, value=0):
|
||||||
|
self.x = value
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.x = struct.unpack("<f", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<f", self.x))
|
||||||
|
|
||||||
|
class float2(float):
|
||||||
|
|
||||||
|
y: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.y = struct.unpack("<f", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<f", self.y))
|
||||||
|
|
||||||
|
class float3(float2):
|
||||||
|
z: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.z = struct.unpack("<f", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<f", self.z))
|
||||||
|
|
||||||
|
class float4(float3):
|
||||||
|
w: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self, data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
|
self.w = struct.unpack("<f", data.read(4))[0]
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
data.write(struct.pack("<f", self.w))
|
||||||
|
|
||||||
|
class float2x2(ResoType):
|
||||||
|
m00: float = 0
|
||||||
|
m01: float = 0
|
||||||
|
m10: float = 0
|
||||||
|
m11: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.m00,self.m01,self.m10,self.m11 = struct.unpack("<ffff", data.read(4*(2*2)))
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<ffff", self.m00,self.m01, self.m10,self.m11))
|
||||||
|
|
||||||
|
|
||||||
|
class float3x3(float2x2):
|
||||||
|
m02: float = 0
|
||||||
|
m12: float = 0
|
||||||
|
m22: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.m00,self.m01,self.m02,self.m10,self.m11,self.m12,self.m20,self.m21,self.m22 = struct.unpack("<fffffffff", data.read(4*(3*3)))
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<fffffffff", self.m00,self.m01,self.m02,self.m10,self.m11,self.m12,self.m20,self.m21,self.m22))
|
||||||
|
|
||||||
|
|
||||||
|
class float4x4(float3x3):
|
||||||
|
m03: float = 0
|
||||||
|
m13: float = 0
|
||||||
|
m23: float = 0
|
||||||
|
m33: float = 0
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read(self,data: BytesIO):
|
||||||
|
self.m00,self.m01,self.m02,self.m03,self.m10,self.m11,self.m12,self.m13,self.m20,self.m21,self.m22,self.m23,self.m30,self.m31,self.m32,self.m33 = struct.unpack("<ffffffffffffffff", data.read(4*(4*4)))
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
data.write(struct.pack("<ffffffffffffffff", self.m00,self.m01,self.m02,self.m03,self.m10,self.m11,self.m12,self.m13,self.m20,self.m21,self.m22,self.m23,self.m30,self.m31,self.m32,self.m33))
|
||||||
|
|
||||||
|
class floatQ(float4):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write(self, data: BytesIO):
|
||||||
|
super().write(data)
|
||||||
|
|
||||||
|
def read(self, data: BytesIO):
|
||||||
|
super().read(data)
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
from types import FrameType
|
||||||
|
import bpy
|
||||||
|
import bpy_extras
|
||||||
|
from numpy import double
|
||||||
|
|
||||||
|
from .common import get_armature, get_selected_armature, simplify_bonename, is_valid_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
|
||||||
|
from ..core.dictionaries import bone_names
|
||||||
|
|
||||||
|
import re
|
||||||
|
from .resonite_loader import resonite_animx, resonite_types
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@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'}
|
||||||
|
|
||||||
|
@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("<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>"
|
||||||
|
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'}
|
||||||
|
|
||||||
|
|
||||||
|
def makeorexistingfcurve(action: bpy.types.Action,data_path: str,action_group: str, index=0) -> bpy.types.FCurve:
|
||||||
|
fcurve = action.fcurves.find(data_path=data_path,index=index)
|
||||||
|
if fcurve == None:
|
||||||
|
return action.fcurves.new(data_path,action_group=action_group,index=index)
|
||||||
|
else:
|
||||||
|
print("fcurve with data \""+data_path+"\" already exists")
|
||||||
|
return fcurve
|
||||||
|
|
||||||
|
@register_wrap
|
||||||
|
class AvatarToolKit_OT_AnimX_Importer(Operator,bpy_extras.io_utils.ImportHelper):
|
||||||
|
bl_idname = 'avatar_toolkit.animx_importer'
|
||||||
|
bl_label = t('Tools.animx_importer.label')
|
||||||
|
bl_description = t('Tools.animx_importer.desc')
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
#fps = bpy.props.FloatProperty(default=25) #25 fps
|
||||||
|
|
||||||
|
filter_glob: bpy.props.StringProperty(
|
||||||
|
default="*.animx",
|
||||||
|
options={'HIDDEN'}
|
||||||
|
)
|
||||||
|
files: bpy.props.CollectionProperty(type=bpy.types.OperatorFileListElement, options={'HIDDEN', 'SKIP_SAVE'})
|
||||||
|
filepath: bpy.props.StringProperty()
|
||||||
|
|
||||||
|
directory:bpy.props.StringProperty(subtype='DIR_PATH')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context: Context) -> bool:
|
||||||
|
return context.active_object != None
|
||||||
|
|
||||||
|
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 = 30 #should be default fps
|
||||||
|
froox_animation.read(file = os.path.join(self.directory,file))
|
||||||
|
Froox_animations.append(froox_animation)
|
||||||
|
|
||||||
|
#TODO: Allow multiple targets and setting animations to each one somehow with an interface.
|
||||||
|
target: bpy.types.Object = context.active_object
|
||||||
|
if target.animation_data == None:
|
||||||
|
target.animation_data_create()
|
||||||
|
|
||||||
|
#Load data into Blender Animations.
|
||||||
|
for froox_animation in Froox_animations:
|
||||||
|
action: bpy.types.Action = bpy.data.actions.new(froox_animation.name.x)
|
||||||
|
target.animation_data.action = action
|
||||||
|
action.use_fake_user = True
|
||||||
|
for track in froox_animation.tracks:
|
||||||
|
data_path: str
|
||||||
|
actualproperty: str = track.property.x
|
||||||
|
|
||||||
|
match(actualproperty):
|
||||||
|
case("Position"):
|
||||||
|
actualproperty = "location"
|
||||||
|
case("Rotation"):
|
||||||
|
actualproperty = "rotation_quaternion"
|
||||||
|
case("Scale"):
|
||||||
|
actualproperty = "scale"
|
||||||
|
data_path = actualproperty
|
||||||
|
|
||||||
|
if target.type == "ARMATURE":
|
||||||
|
data_path = "pose.bones[\""+track.node.x+"\"]."+data_path
|
||||||
|
|
||||||
|
for posebone in target.pose.bones:
|
||||||
|
posebone.rotation_mode = "QUATERNION"
|
||||||
|
|
||||||
|
print("reading frames for "+data_path)
|
||||||
|
if(track.FrameType == "resonite_types.double" or track.FrameType == "resonite_types.double"):
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=0),".x")
|
||||||
|
elif (track.FrameType == "resonite_types.float3" or track.FrameType == "resonite_types.double3"):
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=0),".x")
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=2),".y")
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=1),".z")
|
||||||
|
elif (track.FrameType == "resonite_types.float4" or track.FrameType == "resonite_types.double4"):
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=0),".x")
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=1),".y")
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=2),".z")
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=3),".w")
|
||||||
|
elif (track.FrameType == "resonite_types.doubleQ" or track.FrameType == "resonite_types.floatQ"):
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=3),".w")
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=0),".x")
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=2),".y")
|
||||||
|
self.readTrackData(track,makeorexistingfcurve(action=action,data_path=data_path,action_group=track.node.x,index=1),".z")
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
def readTrackData(self,track: resonite_animx.ResoTrack, fcurve_reso: bpy.types.FCurve, valuetype: str = ""):
|
||||||
|
tracktype = type(track)
|
||||||
|
match(tracktype):
|
||||||
|
case (resonite_animx.RawTrack):
|
||||||
|
rawtrack: resonite_animx.RawTrack = track
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fcurve_reso.keyframe_points.add(count=len(rawtrack.keyframes))
|
||||||
|
# populate points
|
||||||
|
fcurve_reso.keyframe_points.foreach_set("co", [x for co in zip([frame.time.x*track.Owner.interval.x for frame in rawtrack.keyframes], [eval("frame.value"+valuetype) for frame in rawtrack.keyframes]) for x in co])
|
||||||
|
fcurve_reso.update()
|
||||||
|
|
||||||
|
case (resonite_animx.DiscreteTrack):
|
||||||
|
discretetrack: resonite_animx.DiscreteTrack = track
|
||||||
|
|
||||||
|
fcurve_reso.keyframe_points.add(count=len(discretetrack.keyframes))
|
||||||
|
# populate points
|
||||||
|
fcurve_reso.keyframe_points.foreach_set("co", [x for co in zip([frame.time.x*track.Owner.interval.x for frame in discretetrack.keyframes], [eval("frame.value"+valuetype) for frame in discretetrack.keyframes]) for x in co])
|
||||||
|
fcurve_reso.update()
|
||||||
|
|
||||||
|
case(resonite_animx.CurveTrack):
|
||||||
|
curvetrack: resonite_animx.CurveTrack = track
|
||||||
|
|
||||||
|
fcurve_reso.keyframe_points.add(count=len(curvetrack.keyframes))
|
||||||
|
# populate points
|
||||||
|
fcurve_reso.keyframe_points.foreach_set("co", [x for co in zip([frame.time.x*track.Owner.interval.x for frame in curvetrack.keyframes], [eval("frame.value"+valuetype) for frame in curvetrack.keyframes]) for x in co])
|
||||||
|
interp: bool = curvetrack.tangents
|
||||||
|
#print("has tangents? "+str(interp))
|
||||||
|
|
||||||
|
for idx,frame in enumerate(curvetrack.keyframes):
|
||||||
|
|
||||||
|
if interp:
|
||||||
|
fcurve_reso.keyframe_points[idx].handle_left = float(eval("frame.left_tan"+valuetype))
|
||||||
|
fcurve_reso.keyframe_points[idx].handle_right = float(eval("frame.right_tan"+valuetype))
|
||||||
|
fcurve_reso.keyframe_points[idx].interpolation = "BEZIER"
|
||||||
|
fcurve_reso.keyframe_points[idx].easing = "EASE_IN"
|
||||||
|
fcurve_reso.update()
|
||||||
|
|
||||||
|
case(resonite_animx.BezierTrack):
|
||||||
|
beziertrack: resonite_animx.BezierTrack = track
|
||||||
|
# Bezier is not supported rn, ignore.
|
||||||
|
case _:
|
||||||
|
print("invalid track type, ignoring")
|
||||||
|
print(track)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
from typing import TypedDict
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Operator, Object, Context, Mesh, MeshUVLoopLayer
|
||||||
|
import bmesh
|
||||||
|
import numpy as np
|
||||||
|
import math
|
||||||
|
from ..functions.translations import t
|
||||||
|
from ..core.register import register_wrap
|
||||||
|
|
||||||
|
class GenerateLoopTreeResult(TypedDict):
|
||||||
|
tree: dict[str, set[str]]
|
||||||
|
selected_loops: dict[str,list[int]]
|
||||||
|
selected_verts: dict[str,int]
|
||||||
|
|
||||||
|
@register_wrap
|
||||||
|
class AvatarToolkit_OT_AlignUVEdgesToTarget(Operator):
|
||||||
|
bl_idname = "avatar_toolkit.align_uv_edges_to_target"
|
||||||
|
bl_label = t("avatar_toolkit.align_uv_edges_to_target.label")
|
||||||
|
bl_description = t("avatar_toolkit.align_uv_edges_to_target.desc")
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#all selected objects need to be meshes for this to work - @989onan
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context: Context):
|
||||||
|
if not ((context.view_layer.objects.active is not None) and (len(context.view_layer.objects.selected) > 0)):
|
||||||
|
return False
|
||||||
|
if context.mode != "EDIT_MESH":
|
||||||
|
return False
|
||||||
|
for obj in context.view_layer.objects.selected:
|
||||||
|
if obj.type != "MESH":
|
||||||
|
return False
|
||||||
|
if not context.space_data:
|
||||||
|
return False
|
||||||
|
if not context.space_data.show_uvedit:
|
||||||
|
return False
|
||||||
|
if context.scene.tool_settings.use_uv_select_sync:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def execute(self, context: Context):
|
||||||
|
|
||||||
|
|
||||||
|
target: str = context.view_layer.objects.active.name #The object which we want to align every other selected object's selected UV vertex line to
|
||||||
|
|
||||||
|
sources: list[str] = [i.name for i in context.view_layer.objects.selected] #The objects which we want to align their selected UV lines to the target's UV line
|
||||||
|
|
||||||
|
prev_mode: str = bpy.context.object.mode
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
|
||||||
|
|
||||||
|
def generate_loop_tree(obj_name: str) -> GenerateLoopTreeResult:
|
||||||
|
print("Finding selected line for: \""+obj_name+"\"!")
|
||||||
|
|
||||||
|
|
||||||
|
vert_target_loops: dict[str,list[int]] = {}
|
||||||
|
vert_target_verts: dict[str,int] = {}
|
||||||
|
|
||||||
|
me: Mesh = bpy.data.objects[obj_name].data
|
||||||
|
uv_lay: MeshUVLoopLayer = me.uv_layers.active
|
||||||
|
bm: bmesh.types.BMesh = bmesh.new()
|
||||||
|
bm.from_mesh(me)
|
||||||
|
bm.verts.ensure_lookup_table()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# To explain:
|
||||||
|
# So loops in UV maps are X polygons that make up a face (So a MeshLoop represent a face and each vertex on that face is in order)
|
||||||
|
#
|
||||||
|
# For some preknowledge:
|
||||||
|
# When a mesh is UV unwrapped, if a vertice is shared by two different faces on the model in the viewport and the vertice of both faces are in
|
||||||
|
# the same position on the UV map, then it considers it one point and the user can move it
|
||||||
|
# (is why the uv map doesn't split apart when you try to move a vertex because that would be annoying)
|
||||||
|
#
|
||||||
|
# The problem:
|
||||||
|
# The problem is that the data for whether the uv corners of two faces that share a vertex physically being connected and selected as one vertex on the uv map does not exist
|
||||||
|
# Though thankfully, blender forcibly (whether you like it or not) merges vertices of a uv map if the vertex of two different faces are actually shared in the UI,
|
||||||
|
# allowing for the moving of vertices of 4 faces connected by a single vertex. Behavior every normal blender user is familiar with.
|
||||||
|
#
|
||||||
|
# The solution
|
||||||
|
# We can use this to our advantage, by finding vertices on the uv map that share the same coridinate as another vertex that is also selected.
|
||||||
|
# that way we can group each pair shared in a line as the same vertex, and identify the line using these pairs and using the data that says for certain
|
||||||
|
# that two vertices share the same face loop, and therefore are connected.
|
||||||
|
|
||||||
|
#hmmm real stupid grimlin hours with this one. Using a string as the index of a dictionary of loop corners that end up on the same coordinate
|
||||||
|
|
||||||
|
for k,i in enumerate(uv_lay.vertex_selection): #go through the selected vertices on object.
|
||||||
|
if (i.value == True) and (bm.verts[me.loops[k].vertex_index].select == True) and (bm.verts[me.loops[k].vertex_index].hide == False): #filter out vertices that are hidden from UV port
|
||||||
|
key = np.array(uv_lay.uv[k].vector[:])
|
||||||
|
key = key.round(decimals=5) #make a key that is the position of a selected vertex
|
||||||
|
|
||||||
|
if str(key) not in vert_target_loops:
|
||||||
|
vert_target_loops[str(key)] = [] #if the vertex's position is not a list yet, add it.
|
||||||
|
vert_target_loops[str(key)].append(k) #Basically, group vertices based on their position on a UV map as a list.
|
||||||
|
vert_target_verts[str(key)] = me.loops[k].vertex_index #associate the index of the physical vertex in real space with the coordinate of the uv vertices that share a position (Basically associate UV vert with real vert)
|
||||||
|
if len(vert_target_loops) > 4000: #This usually indicates that the user has a bunch of crap selected.
|
||||||
|
self.report({'WARNING'}, t("UVTools.align_uv_to_target.warning.too_much"))
|
||||||
|
return
|
||||||
|
print("Finding connections on line for \""+obj_name+"\"!")
|
||||||
|
me.validate()
|
||||||
|
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(me)
|
||||||
|
|
||||||
|
|
||||||
|
#print(vert_target_loops)
|
||||||
|
#print(vert_target_verts)
|
||||||
|
tree: dict[str, set[str]] = {}
|
||||||
|
selected_verts = np.hstack(list(vert_target_loops.values()))
|
||||||
|
#print(selected_verts)
|
||||||
|
bm.verts.ensure_lookup_table()
|
||||||
|
for uvcoordsstr in vert_target_loops:
|
||||||
|
|
||||||
|
uv_lay = me.uv_layers.active
|
||||||
|
|
||||||
|
|
||||||
|
#before this section, each vert_target_loops is just groupings of vertices that share coordinates.
|
||||||
|
# Using the data that determines UV face corners (uvloops) that are associated with the real vertex,
|
||||||
|
# and the uv face corners (loops) that are on the same faces as the vertices that share coordinates in
|
||||||
|
# vert_target_loops, we can now identify them
|
||||||
|
#TL;DR: pairs of vertices that share cooridinates (chain links) find their buddies (make chain connected)
|
||||||
|
|
||||||
|
# Someone explain this better than me if you can please - @989onan
|
||||||
|
extension_loops = []
|
||||||
|
loops = bm.verts[vert_target_verts[uvcoordsstr]].link_loops
|
||||||
|
loops_indexes = [i.index for i in loops]
|
||||||
|
for loop in vert_target_loops[uvcoordsstr]:
|
||||||
|
if loop in loops_indexes:
|
||||||
|
loop_obj = loops[loops_indexes.index(loop)]
|
||||||
|
extension_loops.append(loop_obj.link_loop_next.index)
|
||||||
|
extension_loops.append(loop_obj.link_loop_prev.index)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#make a tree out of the vertices we identified as sharing faces with the vertices in vert_target_loops, and then link them together in a dictionary.
|
||||||
|
#the order of this dictionary is unknown.
|
||||||
|
# Someone explain this better than me if you can please - @989onan
|
||||||
|
tree[uvcoordsstr] = set()
|
||||||
|
|
||||||
|
for i in extension_loops:
|
||||||
|
if i in selected_verts:
|
||||||
|
key = np.array(uv_lay.uv[i].vector[:])
|
||||||
|
key = key.round(decimals=5)
|
||||||
|
tree[uvcoordsstr].add(str(key))
|
||||||
|
|
||||||
|
if uvcoordsstr in tree:
|
||||||
|
if len(tree[uvcoordsstr]) > 2:
|
||||||
|
self.report({'WARNING'}, t("UVTools.align_uv_to_target.warning.need_a_line").format(obj=obj_name))
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
uv_lay = me.uv_layers.active
|
||||||
|
for uvcoordstr in vert_target_loops:
|
||||||
|
for loop in vert_target_loops[uvcoordstr]:
|
||||||
|
uv_lay.vertex_selection[loop].value = True
|
||||||
|
|
||||||
|
|
||||||
|
bm.free()
|
||||||
|
me.validate()
|
||||||
|
print("found UV line connections for \""+obj_name+"\":")
|
||||||
|
#print(tree)
|
||||||
|
|
||||||
|
return {"tree":tree,"selected_loops":vert_target_loops,"selected_verts":vert_target_verts}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#This function uses the previous point to find the next point based on connected loops and faces.
|
||||||
|
def sort_uv_tree(originaltree: dict[str, set[str]], obj_name: str):
|
||||||
|
sortedtree: dict[str, set[str]] = originaltree.copy()
|
||||||
|
startpoints: list[str] = []
|
||||||
|
for i in sortedtree:
|
||||||
|
if len(sortedtree[i]) < 2:
|
||||||
|
startpoints.append(i)
|
||||||
|
|
||||||
|
if len(startpoints) != 2:
|
||||||
|
self.report({'WARNING'}, t("UVTools.align_uv_to_target.warning.need_a_line").format(obj=obj_name))
|
||||||
|
return
|
||||||
|
|
||||||
|
a_list1 = startpoints[0].replace(", "," ").replace("[","").replace("]","").split()
|
||||||
|
map_object1 = map(float, a_list1)
|
||||||
|
uvcoords1 = list(map_object1)
|
||||||
|
a_list2 = startpoints[1].replace(", "," ").replace("[","").replace("]","").split()
|
||||||
|
map_object2 = map(float, a_list2)
|
||||||
|
uvcoords2 = list(map_object2)
|
||||||
|
|
||||||
|
cursor = context.space_data.cursor_location
|
||||||
|
|
||||||
|
startpoint = None
|
||||||
|
if math.sqrt( (((uvcoords1[0]) - (cursor[0])) **2) + (((uvcoords1[1]) - (cursor[1])) **2) ) > math.sqrt( (((uvcoords2[0]) - (cursor[0])) **2) + (((uvcoords2[1]) - (cursor[1])) **2) ):
|
||||||
|
startpoint = startpoints[0]
|
||||||
|
else:
|
||||||
|
startpoint = startpoints[1]
|
||||||
|
|
||||||
|
#Wew my first actual recursive sort! - @989onan
|
||||||
|
def recursive_sort_uv_tree(point: str, sortedfinal: list[str]):
|
||||||
|
#print("appending "+point)
|
||||||
|
sortedfinal.append(point)
|
||||||
|
|
||||||
|
new_point: str = ""
|
||||||
|
for i in sortedtree:
|
||||||
|
if point in sortedtree[i]:
|
||||||
|
new_point = i
|
||||||
|
removed_value = sortedtree.pop(i)
|
||||||
|
#print(removed_value)
|
||||||
|
break
|
||||||
|
|
||||||
|
if new_point == "":
|
||||||
|
print("BROKE OUT OF SORTING, FINAL TREE (Should be empty, if not you errored here!):")
|
||||||
|
print(sortedtree)
|
||||||
|
|
||||||
|
return sortedfinal
|
||||||
|
|
||||||
|
return recursive_sort_uv_tree(new_point, sortedfinal)
|
||||||
|
|
||||||
|
array = []
|
||||||
|
|
||||||
|
sortedtree.pop(startpoint)
|
||||||
|
return recursive_sort_uv_tree(startpoint, array)
|
||||||
|
|
||||||
|
def lerp(v0, v1, t):
|
||||||
|
return v0 + t * (v1 - v0)
|
||||||
|
|
||||||
|
|
||||||
|
target_data: GenerateLoopTreeResult = generate_loop_tree(target)
|
||||||
|
sorted_target_tree = sort_uv_tree(target_data["tree"], target)
|
||||||
|
print("sorted target.")
|
||||||
|
#print(sorted_target_tree)
|
||||||
|
|
||||||
|
for source in sources:
|
||||||
|
if source == target:
|
||||||
|
continue
|
||||||
|
|
||||||
|
#create our list of points that is a chain. then sort the chain into the correct order based on connections of vertices and the faces that the vertices make up in the UV map.
|
||||||
|
try:
|
||||||
|
source_data = generate_loop_tree(source)
|
||||||
|
sorted_source_tree = sort_uv_tree(source_data["tree"], source)
|
||||||
|
print("Sorted source "+source)
|
||||||
|
print(sorted_source_tree)
|
||||||
|
|
||||||
|
vertex_factor = float(len(sorted_target_tree)-1) / (float(len(sorted_source_tree)-1))
|
||||||
|
|
||||||
|
print(str(vertex_factor)+" = "+str(float(len(sorted_target_tree)-1)) + " / " + str((float(len(sorted_source_tree)-1)))+")")
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
for k,i in enumerate(sorted_source_tree):
|
||||||
|
|
||||||
|
try:
|
||||||
|
#find where we are on the target edges, to interpolate the current point we're placing along the target point's line.
|
||||||
|
progress_along_edge = (float(k)*vertex_factor)
|
||||||
|
previous_vertex_index = math.floor(progress_along_edge)
|
||||||
|
next_vertex_index = math.ceil(progress_along_edge)
|
||||||
|
|
||||||
|
|
||||||
|
#find the uv coordinates of the previous and next points on the target uv line.
|
||||||
|
a_list1 = sorted_target_tree[previous_vertex_index].replace(", "," ").replace("[","").replace("]","").split()
|
||||||
|
map_object1 = map(float, a_list1)
|
||||||
|
previous_point = list(map_object1)
|
||||||
|
a_list2 = sorted_target_tree[next_vertex_index].replace(", "," ").replace("[","").replace("]","").split()
|
||||||
|
map_object2 = map(float, a_list2)
|
||||||
|
next_point = list(map_object2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#create a point between these two values that represents a decimal 0-1 going where we are to where we are going between the two current points on the edge we are targeting this whole shebang with.
|
||||||
|
progress_between_points = progress_along_edge - int(progress_along_edge)
|
||||||
|
lerped_point = [lerp(previous_point[0],next_point[0],progress_between_points),lerp(previous_point[1],next_point[1],progress_between_points)]
|
||||||
|
|
||||||
|
#grab our uv face corners for each uv coord that we saved.
|
||||||
|
#Since each face is considered separate internally, we have to treat each connected face to a vertex in a uv map as separate entities/vertexes.
|
||||||
|
#basically pretend they are split apart.
|
||||||
|
uv_face_corners = source_data["selected_loops"][i]
|
||||||
|
#print("doing from vertex "+str(previous_vertex_index)+" to "+str(next_vertex_index)+" total progress: "+str(progress_along_edge))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
me: Mesh = bpy.data.objects[source].data
|
||||||
|
me.validate()
|
||||||
|
bm: bmesh.types.BMesh = bmesh.new()
|
||||||
|
bm.from_mesh(me)
|
||||||
|
uv_lay: MeshUVLoopLayer = me.uv_layers.active
|
||||||
|
bm.verts.ensure_lookup_table()
|
||||||
|
for corner in uv_face_corners:
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bpy.ops.object.mode_set(mode=prev_mode)
|
||||||
|
return {'FINISHED'}
|
||||||
Reference in New Issue
Block a user