AnimX importer done

Finished the importer for now, it may contain errors but those would be part of the library
This commit is contained in:
989onan
2024-12-15 16:09:18 -05:00
parent ff4ae71832
commit 2337449a77
6 changed files with 287 additions and 212 deletions
-49
View File
@@ -502,56 +502,7 @@ def transfer_vertex_weights(context: Context, obj: bpy.types.Object, source_grou
#Binary tools #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('<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) -> 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('<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: ctypes.c_ulong) -> 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: #encoding FrooxEngine/C# types in binary:
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"language": 0, "language": 0,
"last_update_check": 1734208835.8049936 "last_update_check": 1734295375.2681296
} }
+14 -5
View File
@@ -1,12 +1,21 @@
import ctypes import ctypes
import typing import typing
import struct import struct
from io import BytesIO 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: def ReadCSharp_str(data: BytesIO) -> str:
charamount = read7bitEncoded_int(data) charamount = read7bitEncoded_int(data)
string: str = data.read(charamount).decode('utf-8', errors="replace") string: str = data.read(charamount).decode('utf-8', errors="replace")
print("read string: "+string) #print("read string: "+string)
return string return string
def WriteCSharp_str(data: BytesIO, string: str) -> str: def WriteCSharp_str(data: BytesIO, string: str) -> str:
@@ -29,7 +38,7 @@ def read7bitEncoded_ulong(data: BytesIO) -> int:
return num return num
def read7bitEncoded_int(data: BytesIO) -> int: def read7bitEncoded_int(data: BytesIO) -> int:
num: int= int(0) num: int = int(0)
num2:int = int(0) num2:int = int(0)
while (num2 != 35): while (num2 != 35):
b: int = int(struct.unpack('<B', data.read(1))[0]) b: int = int(struct.unpack('<B', data.read(1))[0])
@@ -41,7 +50,7 @@ def read7bitEncoded_int(data: BytesIO) -> int:
def write7bitEncoded_ulong(data: BytesIO, integer: int) -> None: def write7bitEncoded_ulong(data: BytesIO, integer: int) -> None:
while integer > int(0): while integer > int(0):
b: int = ctypes.c_ubyte(integer & int(127)) b: int = int(integer & int(127))
integer >>= 7 integer >>= 7
if integer > int(0): if integer > int(0):
b |= 128 b |= 128
@@ -54,4 +63,4 @@ def write7bitEncoded_int(data: BytesIO, value: int) -> None:
while(num >= int(128)): while(num >= int(128)):
data.write(int(num | int(128))) data.write(int(num | int(128)))
num >>= 7 num >>= 7
data.Write(int(num)) data.Write(int(num))
+154 -110
View File
@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
from os import replace from os import replace
from re import S
from types import FrameType from types import FrameType
import lz4.block import lz4.block
@@ -10,6 +11,7 @@ import typing
import struct import struct
from io import BytesIO from io import BytesIO
KeyframeInterpolation: dict[str, int] = { KeyframeInterpolation: dict[str, int] = {
"Hold": 1, "Hold": 1,
"Linear": 2, "Linear": 2,
@@ -18,28 +20,17 @@ KeyframeInterpolation: dict[str, int] = {
} }
class KeyFrame(): class KeyFrame():
time: resonite_types.float = resonite_types.float(0) time: resonite_types.float
interpolation: resonite_types.int = resonite_types.int(0) interpolation: resonite_types.byte
value: resonite_types.ResoType value: resonite_types.ResoType
left_tan: resonite_types.ResoType left_tan: resonite_types.ResoType
right_tan: resonite_types.ResoType right_tan: resonite_types.ResoType
def __getattr__(self, name: str):
if name == "interpolation":
interp: int = 0
if (self["left_tan"] != None and self["right_tan"] != None):
interp = 3
return resonite_types.int(interp)
return super().__getattribute__(name)
def __init__(self): def __init__(self):
pass self.time = resonite_types.float(0)
self.interpolation = resonite_types.byte(0)
def RequiresTangents(self) -> bool: def RequiresTangents(self) -> bool:
@@ -48,14 +39,17 @@ class KeyFrame():
return False return False
class ResoTrack(resonite_types.ResoType): class ResoTrack(resonite_types.ResoType):
node: resonite_types.string = resonite_types.string("") node: resonite_types.string
property: resonite_types.string = resonite_types.string("") property: resonite_types.string
Owner: AnimX Owner: AnimX
FrameType: type[resonite_types.ResoType] FrameType: str
keyframes: list[KeyFrame] = [] keyframes: list[KeyFrame]
def __init__(self,FrameType): def __init__(self,FrameType):
self.FrameType = FrameType self.FrameType = FrameType
self.keyframes = []
self.node = resonite_types.string("")
self.property = resonite_types.string("")
def write(self, data: BytesIO): def write(self, data: BytesIO):
self.node.write(data) self.node.write(data)
@@ -65,10 +59,12 @@ class ResoTrack(resonite_types.ResoType):
def read(self, data:BytesIO): def read(self, data:BytesIO):
self.node.read(data) self.node.read(data)
self.property.read(data) self.property.read(data)
track_amount: int = int(common.read7bitEncoded_ulong(data)) track_amount: int = int(common.read7bitEncoded_ulong(data))
#print(track_amount)
for i in range(0, track_amount): for i in range(0, track_amount):
key: KeyFrame = KeyFrame() key: KeyFrame = KeyFrame()
key.value = self.FrameType() key.value = eval(self.FrameType+"()")
self.keyframes.append(key) self.keyframes.append(key)
def removeKeyframe(self, time: float | int) -> bool: def removeKeyframe(self, time: float | int) -> bool:
@@ -143,28 +139,35 @@ class ResoTrack(resonite_types.ResoType):
class RawTrack(ResoTrack): class RawTrack(ResoTrack):
interval: resonite_types.float = resonite_types.float(0) interval: resonite_types.float
def __getattr__(self, name: str): def __getattr__(self, name: str):
if name == "interval": if name == "interval":
return self.Owner.interval.x return self.Owner.interval
return super().__getattribute__(name) return super().__getattribute__(name)
def __init__(self, FrameType): def __init__(self, FrameType):
super().__init__(FrameType) super().__init__(FrameType)
self.interval = resonite_types.float(0)
def write(self, data: BytesIO): def write(self, data: BytesIO):
super().write(data) super().write(data)
self.interval.write(data) self.interval.write(data)
for key in self.keyframes: for key in self.keyframes:
key.value.write(data) if self.FrameType == "resonite_types.string":
resonite_types.writeNullable(data, key.value)
else:
key.value.write(data)
def read(self, data:BytesIO): def read(self, data:BytesIO):
super().read(data) super().read(data)
self.interval.read(data) self.interval.read(data)
for key in self.keyframes: for key in self.keyframes:
key.value.read(data) if self.FrameType == "resonite_types.string":
resonite_types.readNullable(data, key.value)
else:
key.value.read(data)
def addKeyframe(self, keyframe: KeyFrame) -> int: def addKeyframe(self, keyframe: KeyFrame) -> int:
num: int = super().addKeyframe(keyframe) num: int = super().addKeyframe(keyframe)
@@ -187,10 +190,28 @@ class DiscreteTrack(ResoTrack):
def write(self, data: BytesIO): def write(self, data: BytesIO):
super().write(data) 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): def read(self, data:BytesIO):
super().read(data) 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: def addKeyframe(self, keyframe: KeyFrame) -> int:
num: int = super().addKeyframe(keyframe) num: int = super().addKeyframe(keyframe)
@@ -203,23 +224,27 @@ class DiscreteTrack(ResoTrack):
class CurveTrack(ResoTrack): class CurveTrack(ResoTrack):
interpolations: bool = False interpolations: bool
tangents: bool = False tangents: bool
sharedinterpolation: resonite_types.int = resonite_types.int(-1) sharedinterpolation: resonite_types.byte
def __getattr__(self, name: str): def __getattr__(self, name: str):
if name == "interpolations": if name == "interpolations":
integerframe: int = self.keyframes[0].interpolation.x
for key in self.keyframes: for key in self.keyframes:
if key.interpolation.x != self.sharedinterpolation.x: if key.interpolation.x != integerframe:
return True return True
elif name == "tangents": elif name == "tangents":
for key in self.keyframes: for key in self.keyframes:
if key.interpolation.x == 3 or key.interpolation.x == 4: if key.RequiresTangents():
return True return True
return super().__getattribute__(name) return super().__getattribute__(name)
def __init__(self, FrameType): def __init__(self, FrameType):
super().__init__(FrameType) super().__init__(FrameType)
self.sharedinterpolation = resonite_types.byte(-1)
self.interpolations = False
self.tangents = False
def write(self, data: BytesIO): def write(self, data: BytesIO):
super().write(data) super().write(data)
@@ -234,45 +259,57 @@ class CurveTrack(ResoTrack):
for key in self.keyframes: for key in self.keyframes:
if key.value == None: if key.value == None:
key.value = self.FrameType() key.value = eval(self.FrameType+"()")
key.value.write(data) if self.FrameType == "resonite_types.string":
resonite_types.writeNullable(data, key.value)
else:
key.value.write(data)
key.time.write(data) key.time.write(data)
if(self.tangents): if(self.tangents):
for key in self.keyframes: for key in self.keyframes:
key.left_tan.write(data) if self.FrameType == "resonite_types.string":
key.right_tan.write(data) 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): def read(self, data:BytesIO):
super().read(data) super().read(data)
flags: int = struct.unpack("<B",data.read(1))[0] flags: int = struct.unpack("<B",data.read(1))[0]
self.interpolations = (flags & 1) > 0 interp: bool = (flags & 1) > 0
self.tangents = (flags & 2) > 0 tan: bool = (flags & 2) > 0
print(str(self.interpolations)) #print(str(interp))
print(str(self.tangents)) #print(str(tan))
#print(flags)
#print(len(self.keyframes))
if(self.interpolations): if(interp):
for key in self.keyframes: for key in self.keyframes:
#print("reading interp")
key.interpolation.read(data) key.interpolation.read(data)
else: else:
self.sharedinterpolation.read(data) self.sharedinterpolation.read(data)
for key in self.keyframes: for key in self.keyframes:
print("key read!")
if key.value == None: if key.value == None:
key.value = self.FrameType() key.value = eval(self.FrameType+"()")
key.value.read(data) if self.FrameType == "resonite_types.string":
resonite_types.readNullable(data, key.value)
else:
key.value.read(data)
key.time.read(data) key.time.read(data)
if(self.tangents): if(tan):
for key in self.keyframes: for key in self.keyframes:
if key.left_tan == None: if self.FrameType == "resonite_types.string":
key.left_tan = self.FrameType() resonite_types.readNullable(data, key.left_tan)
if key.right_tan == None: resonite_types.readNullable(data, key.right_tan)
key.right_tan = self.FrameType() else:
key.left_tan.read(data) key.left_tan.read(data)
key.right_tan.read(data) key.right_tan.read(data)
@@ -313,56 +350,56 @@ class BezierTrack(ResoTrack):
"""PLACE HOLDER METHOD, DO NOT USE""" """PLACE HOLDER METHOD, DO NOT USE"""
raise Exception("BezierTrack track type is unsupported in resonite's code") raise Exception("BezierTrack track type is unsupported in resonite's code")
#This is weird, but thank you python - @989onan #This is weird, but thank you python - @989onan
TrackTypes: list[type] = [ TrackTypes: list[str] = [
RawTrack, "RawTrack",
DiscreteTrack, "DiscreteTrack",
CurveTrack, "CurveTrack",
BezierTrack "BezierTrack"
] ]
#TODO: add all types here #TODO: add all types here
#wooooo - @989onan #wooooo - @989onan
elementTypes: list[type[resonite_types.ResoType]] = [ elementTypes: list[str] = [
resonite_types.bool, "resonite_types.bool",
resonite_types.bool2, "resonite_types.bool2",
resonite_types.bool3, "resonite_types.bool3",
resonite_types.bool4, "resonite_types.bool4",
resonite_types.byte, "resonite_types.byte",
resonite_types.ushort, "resonite_types.ushort",
resonite_types.uint, "resonite_types.uint",
resonite_types.ulong, "resonite_types.ulong",
resonite_types.sbyte, "resonite_types.sbyte",
resonite_types.short, "resonite_types.short",
resonite_types.int, "resonite_types.int",
resonite_types.long, "resonite_types.long",
resonite_types.int2, "resonite_types.int2",
resonite_types.int3, "resonite_types.int3",
resonite_types.int4, "resonite_types.int4",
resonite_types.uint2, "resonite_types.uint2",
resonite_types.uint3, "resonite_types.uint3",
resonite_types.uint4, "resonite_types.uint4",
resonite_types.long2, "resonite_types.long2",
resonite_types.long3, "resonite_types.long3",
resonite_types.long4, "resonite_types.long4",
resonite_types.float, "resonite_types.float",
resonite_types.float2, "resonite_types.float2",
resonite_types.float3, "resonite_types.float3",
resonite_types.float4, "resonite_types.float4",
resonite_types.floatQ, "resonite_types.floatQ",
resonite_types.float2x2, "resonite_types.float2x2",
resonite_types.float3x3, "resonite_types.float3x3",
resonite_types.float4x4, "resonite_types.float4x4",
resonite_types.double, "resonite_types.double",
resonite_types.double2, "resonite_types.double2",
resonite_types.double3, "resonite_types.double3",
resonite_types.double4, "resonite_types.double4",
resonite_types.doubleQ, "resonite_types.doubleQ",
resonite_types.double2x2, "resonite_types.double2x2",
resonite_types.double3x3, "resonite_types.double3x3",
resonite_types.double4x4, "resonite_types.double4x4",
resonite_types.color, "resonite_types.color",
resonite_types.color32, "resonite_types.color32",
resonite_types.string "resonite_types.string"
] ]
@@ -372,20 +409,27 @@ class AnimX():
""" """
To use Raw Track properly, please set interval (seconds between frames) after reading/creating.\n 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. 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 = resonite_types.int() file_version: resonite_types.int
track_amount: resonite_types.int = resonite_types.int() track_amount: resonite_types.int
global_duration: resonite_types.float = resonite_types.float() global_duration: resonite_types.float
name: resonite_types.string = resonite_types.string() name: resonite_types.string
tracks: list[ResoTrack] = [] tracks: list[ResoTrack]
interval: resonite_types.float = resonite_types.float(1/25) #default value interval: resonite_types.float
def __init__(self): 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 pass
@classmethod @classmethod
@@ -424,7 +468,7 @@ class AnimX():
self.track_amount.x = common.read7bitEncoded_ulong(data) self.track_amount.x = common.read7bitEncoded_ulong(data)
self.global_duration.read(data) self.global_duration.read(data)
print("track amont: "+str(self.track_amount.x)) print("track amount: "+str(self.track_amount.x))
print("file vers: "+str(self.file_version.x)) print("file vers: "+str(self.file_version.x))
self.name.read(data) self.name.read(data)
@@ -453,8 +497,8 @@ class AnimX():
data.read(8) #fuck off stream headers - @989onan data.read(8) #fuck off stream 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)) filelmza: bytes = bytes(AnimX.decompress_lzma(data.read(), lzma.FORMAT_RAW, filters))
#print("binary below:")
print(f"decompressed bytes: {filelmza[:100]}") #print("b'{}'".format(''.join('\\x{:02x}'.format(b) for b in filelmza[:100])))
data = BytesIO(filelmza) data = BytesIO(filelmza)
case _: case _:
raise Exception("Invalid encoding") raise Exception("Invalid encoding")
@@ -515,10 +559,10 @@ class AnimX():
return True return True
@classmethod @classmethod
def GetTrackType(cls, trackType2: int, value_type: type[resonite_types.ResoType], data: BytesIO) -> ResoTrack: def GetTrackType(cls, trackType2: int, value_type: str, data: BytesIO) -> ResoTrack:
Track = TrackTypes[trackType2](value_type) Track: ResoTrack = eval(TrackTypes[trackType2]+"(value_type)")
print(type(Track)) #print(value_type)
print(value_type) #print(type(Track))
Track.read(data) Track.read(data)
return Track return Track
+24 -13
View File
@@ -17,6 +17,19 @@ class ResoType():
def read(cls, data: BytesIO): def read(cls, data: BytesIO):
pass 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. #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): class color(ResoType):
@@ -75,8 +88,8 @@ class byte(ResoType):
def __int__(self): def __int__(self):
return self.x return self.x
def __init__(self): def __init__(self,value=0):
pass self.x = value
def write(self, data: BytesIO): def write(self, data: BytesIO):
data.write(struct.pack("<B", self.x)) data.write(struct.pack("<B", self.x))
@@ -120,8 +133,8 @@ class short(ResoType):
def __int__(self): def __int__(self):
return self.x return self.x
def __init__(self): def __init__(self,value=0):
pass self.x = value
def write(self, data: BytesIO): def write(self, data: BytesIO):
data.write(struct.pack("<h", self.x)) data.write(struct.pack("<h", self.x))
@@ -264,7 +277,7 @@ class int4(int3):
pass pass
def read(self,data: BytesIO): def read(self,data: BytesIO):
super().write(data) super().read(data)
self.w = struct.unpack("<i", data.read(4))[0] self.w = struct.unpack("<i", data.read(4))[0]
def write(self, data: BytesIO): def write(self, data: BytesIO):
@@ -298,7 +311,7 @@ class uint2(uint):
pass pass
def read(self,data: BytesIO): def read(self,data: BytesIO):
super().write(data) super().read(data)
self.y = struct.unpack("<I", data.read(4))[0] self.y = struct.unpack("<I", data.read(4))[0]
def write(self, data: BytesIO): def write(self, data: BytesIO):
@@ -312,7 +325,7 @@ class uint3(uint2):
pass pass
def read(self,data: BytesIO): def read(self,data: BytesIO):
super().write(data) super().read(data)
self.z = struct.unpack("<I", data.read(4))[0] self.z = struct.unpack("<I", data.read(4))[0]
def write(self, data: BytesIO): def write(self, data: BytesIO):
@@ -326,7 +339,7 @@ class uint4(uint3):
pass pass
def read(self,data: BytesIO): def read(self,data: BytesIO):
super().write(data) super().read(data)
self.w = struct.unpack("<I", data.read(4))[0] self.w = struct.unpack("<I", data.read(4))[0]
def write(self, data: BytesIO): def write(self, data: BytesIO):
@@ -529,8 +542,7 @@ class doubleQ(double4):
def write(self, data: BytesIO): def write(self, data: BytesIO):
super().write(data) super().write(data)
@classmethod def read(self, data: BytesIO):
def read(cls, data: BytesIO):
super().read(data) super().read(data)
@@ -587,7 +599,7 @@ class float4(float3):
def __init__(self): def __init__(self):
pass pass
def read(self,data: BytesIO): def read(self, data: BytesIO):
super().read(data) super().read(data)
self.w = struct.unpack("<f", data.read(4))[0] self.w = struct.unpack("<f", data.read(4))[0]
@@ -649,6 +661,5 @@ class floatQ(float4):
def write(self, data: BytesIO): def write(self, data: BytesIO):
super().write(data) super().write(data)
@classmethod def read(self, data: BytesIO):
def read(cls, data: BytesIO):
super().read(data) super().read(data)
+94 -34
View File
@@ -1,5 +1,7 @@
from types import FrameType
import bpy import bpy
import bpy_extras import bpy_extras
from numpy import double
from .common import get_armature, get_selected_armature, simplify_bonename, is_valid_armature from .common import get_armature, get_selected_armature, simplify_bonename, is_valid_armature
from bpy.types import Object, ShapeKey, Mesh, Context, Operator from bpy.types import Object, ShapeKey, Mesh, Context, Operator
@@ -149,6 +151,14 @@ class AvatarToolKit_OT_ConvertToResonite(Operator):
return {'FINISHED'} 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 @register_wrap
class AvatarToolKit_OT_AnimX_Importer(Operator,bpy_extras.io_utils.ImportHelper): class AvatarToolKit_OT_AnimX_Importer(Operator,bpy_extras.io_utils.ImportHelper):
bl_idname = 'avatar_toolkit.animx_importer' bl_idname = 'avatar_toolkit.animx_importer'
@@ -169,7 +179,7 @@ class AvatarToolKit_OT_AnimX_Importer(Operator,bpy_extras.io_utils.ImportHelper)
@classmethod @classmethod
def poll(cls, context: Context) -> bool: def poll(cls, context: Context) -> bool:
return True return context.active_object != None
def execute(self, context: Context) -> set: def execute(self, context: Context) -> set:
@@ -177,62 +187,112 @@ class AvatarToolKit_OT_AnimX_Importer(Operator,bpy_extras.io_utils.ImportHelper)
#decoding using self contained library: #decoding using self contained library:
files = [file.name for file in self.files] files = [file.name for file in self.files]
files.append(self.filepath) #files.append(self.filepath)
for file in files: for file in files:
froox_animation: resonite_animx.AnimX = resonite_animx.AnimX() froox_animation: resonite_animx.AnimX = resonite_animx.AnimX()
froox_animation.interval.x = 1/25 froox_animation.interval.x = 30 #should be default fps
froox_animation.read(file = os.path.join(self.directory,file)) froox_animation.read(file = os.path.join(self.directory,file))
Froox_animations.append(froox_animation) 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. #Load data into Blender Animations.
for froox_animation in Froox_animations: for froox_animation in Froox_animations:
action: bpy.types.Action = bpy.data.actions.new(froox_animation.name.x) action: bpy.types.Action = bpy.data.actions.new(froox_animation.name.x)
target.animation_data.action = action
action.use_fake_user = True action.use_fake_user = True
for track in froox_animation.tracks: for track in froox_animation.tracks:
print("hit here1") data_path: str
print(track.FrameType) actualproperty: str = track.property.x
if(track.FrameType != type(resonite_types.float) and track.FrameType != type(resonite_types.double)):
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 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
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)): fcurve_reso.keyframe_points.add(count=len(rawtrack.keyframes))
case (type(resonite_animx.RawTrack)): # populate points
rawtrack: resonite_animx.RawTrack = track 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
for frame in rawtrack.keyframes:
key: bpy.types.Keyframe = fcurve_reso.keyframe_points.insert(frame.time, float(frame.value))
case (type(resonite_animx.DiscreteTrack)): fcurve_reso.keyframe_points.add(count=len(discretetrack.keyframes))
discretetrack: resonite_animx.RawTrack = track # populate points
for frame in rawtrack.keyframes: 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])
key: bpy.types.Keyframe = fcurve_reso.keyframe_points.insert(frame.time, float(frame.value)) fcurve_reso.update()
case(resonite_animx.CurveTrack):
curvetrack: resonite_animx.CurveTrack = track
case(type(resonite_animx.CurveTrack)): fcurve_reso.keyframe_points.add(count=len(curvetrack.keyframes))
curvetrack: resonite_animx.RawTrack = track # populate points
for frame in curvetrack.keyframes: 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])
key: bpy.types.Keyframe = fcurve_reso.keyframe_points.insert(frame.time, float(frame.value)) interp: bool = curvetrack.tangents
key.handle_left = float(frame.left_tan) #print("has tangents? "+str(interp))
key.handle_left = float(frame.right_tan)
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(type(resonite_animx.BezierTrack)): case(resonite_animx.BezierTrack):
beziertrack: resonite_animx.RawTrack = track beziertrack: resonite_animx.BezierTrack = track
# Bezier is not supported rn, ignore. # Bezier is not supported rn, ignore.
case _:
print("invalid track type, ignoring")
print(track)
return {'FINISHED'}