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
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:
+1 -1
View File
@@ -1,4 +1,4 @@
{
"language": 0,
"last_update_check": 1734208835.8049936
"last_update_check": 1734295375.2681296
}
+14 -5
View File
@@ -1,12 +1,21 @@
import ctypes
import typing
import struct
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)
#print("read string: "+string)
return string
def WriteCSharp_str(data: BytesIO, string: str) -> str:
@@ -29,7 +38,7 @@ def read7bitEncoded_ulong(data: BytesIO) -> int:
return num
def read7bitEncoded_int(data: BytesIO) -> int:
num: int= int(0)
num: int = int(0)
num2:int = int(0)
while (num2 != 35):
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:
while integer > int(0):
b: int = ctypes.c_ubyte(integer & int(127))
b: int = int(integer & int(127))
integer >>= 7
if integer > int(0):
b |= 128
@@ -54,4 +63,4 @@ def write7bitEncoded_int(data: BytesIO, value: int) -> None:
while(num >= int(128)):
data.write(int(num | int(128)))
num >>= 7
data.Write(int(num))
data.Write(int(num))
+154 -110
View File
@@ -1,5 +1,6 @@
from __future__ import annotations
from os import replace
from re import S
from types import FrameType
import lz4.block
@@ -10,6 +11,7 @@ import typing
import struct
from io import BytesIO
KeyframeInterpolation: dict[str, int] = {
"Hold": 1,
"Linear": 2,
@@ -18,28 +20,17 @@ KeyframeInterpolation: dict[str, int] = {
}
class KeyFrame():
time: resonite_types.float = resonite_types.float(0)
interpolation: resonite_types.int = resonite_types.int(0)
time: resonite_types.float
interpolation: resonite_types.byte
value: resonite_types.ResoType
left_tan: resonite_types.ResoType
right_tan: resonite_types.ResoType
def __getattr__(self, name: str):
if name == "interpolation":
interp: int = 0
if (self["left_tan"] != None and self["right_tan"] != None):
interp = 3
return resonite_types.int(interp)
return super().__getattribute__(name)
def __init__(self):
pass
self.time = resonite_types.float(0)
self.interpolation = resonite_types.byte(0)
def RequiresTangents(self) -> bool:
@@ -48,14 +39,17 @@ class KeyFrame():
return False
class ResoTrack(resonite_types.ResoType):
node: resonite_types.string = resonite_types.string("")
property: resonite_types.string = resonite_types.string("")
node: resonite_types.string
property: resonite_types.string
Owner: AnimX
FrameType: type[resonite_types.ResoType]
keyframes: list[KeyFrame] = []
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)
@@ -65,10 +59,12 @@ class ResoTrack(resonite_types.ResoType):
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 = self.FrameType()
key.value = eval(self.FrameType+"()")
self.keyframes.append(key)
def removeKeyframe(self, time: float | int) -> bool:
@@ -143,28 +139,35 @@ class ResoTrack(resonite_types.ResoType):
class RawTrack(ResoTrack):
interval: resonite_types.float = resonite_types.float(0)
interval: resonite_types.float
def __getattr__(self, name: str):
if name == "interval":
return self.Owner.interval.x
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:
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):
super().read(data)
self.interval.read(data)
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:
num: int = super().addKeyframe(keyframe)
@@ -187,10 +190,28 @@ class DiscreteTrack(ResoTrack):
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)
@@ -203,23 +224,27 @@ class DiscreteTrack(ResoTrack):
class CurveTrack(ResoTrack):
interpolations: bool = False
tangents: bool = False
sharedinterpolation: resonite_types.int = resonite_types.int(-1)
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 != self.sharedinterpolation.x:
if key.interpolation.x != integerframe:
return True
elif name == "tangents":
for key in self.keyframes:
if key.interpolation.x == 3 or key.interpolation.x == 4:
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)
@@ -234,45 +259,57 @@ class CurveTrack(ResoTrack):
for key in self.keyframes:
if key.value == None:
key.value = self.FrameType()
key.value.write(data)
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:
key.left_tan.write(data)
key.right_tan.write(data)
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]
self.interpolations = (flags & 1) > 0
self.tangents = (flags & 2) > 0
interp: bool = (flags & 1) > 0
tan: bool = (flags & 2) > 0
print(str(self.interpolations))
print(str(self.tangents))
#print(str(interp))
#print(str(tan))
#print(flags)
#print(len(self.keyframes))
if(self.interpolations):
if(interp):
for key in self.keyframes:
#print("reading interp")
key.interpolation.read(data)
else:
self.sharedinterpolation.read(data)
for key in self.keyframes:
print("key read!")
if key.value == None:
key.value = self.FrameType()
key.value.read(data)
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(self.tangents):
if(tan):
for key in self.keyframes:
if key.left_tan == None:
key.left_tan = self.FrameType()
if key.right_tan == None:
key.right_tan = self.FrameType()
key.left_tan.read(data)
key.right_tan.read(data)
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)
@@ -313,56 +350,56 @@ class BezierTrack(ResoTrack):
"""PLACE HOLDER METHOD, DO NOT USE"""
raise Exception("BezierTrack track type is unsupported in resonite's code")
#This is weird, but thank you python - @989onan
TrackTypes: list[type] = [
RawTrack,
DiscreteTrack,
CurveTrack,
BezierTrack
TrackTypes: list[str] = [
"RawTrack",
"DiscreteTrack",
"CurveTrack",
"BezierTrack"
]
#TODO: add all types here
#wooooo - @989onan
elementTypes: list[type[resonite_types.ResoType]] = [
resonite_types.bool,
resonite_types.bool2,
resonite_types.bool3,
resonite_types.bool4,
resonite_types.byte,
resonite_types.ushort,
resonite_types.uint,
resonite_types.ulong,
resonite_types.sbyte,
resonite_types.short,
resonite_types.int,
resonite_types.long,
resonite_types.int2,
resonite_types.int3,
resonite_types.int4,
resonite_types.uint2,
resonite_types.uint3,
resonite_types.uint4,
resonite_types.long2,
resonite_types.long3,
resonite_types.long4,
resonite_types.float,
resonite_types.float2,
resonite_types.float3,
resonite_types.float4,
resonite_types.floatQ,
resonite_types.float2x2,
resonite_types.float3x3,
resonite_types.float4x4,
resonite_types.double,
resonite_types.double2,
resonite_types.double3,
resonite_types.double4,
resonite_types.doubleQ,
resonite_types.double2x2,
resonite_types.double3x3,
resonite_types.double4x4,
resonite_types.color,
resonite_types.color32,
resonite_types.string
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"
]
@@ -372,20 +409,27 @@ class AnimX():
"""
To use Raw Track properly, please set interval (seconds between frames) after reading/creating.\n
Represents data to be written to or read from an AnimX file.
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 = resonite_types.int()
track_amount: resonite_types.int = resonite_types.int()
global_duration: resonite_types.float = resonite_types.float()
name: resonite_types.string = resonite_types.string()
file_version: resonite_types.int
track_amount: resonite_types.int
global_duration: resonite_types.float
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):
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
@@ -424,7 +468,7 @@ class AnimX():
self.track_amount.x = common.read7bitEncoded_ulong(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))
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
filelmza: bytes = bytes(AnimX.decompress_lzma(data.read(), lzma.FORMAT_RAW, filters))
print(f"decompressed bytes: {filelmza[:100]}")
#print("binary below:")
#print("b'{}'".format(''.join('\\x{:02x}'.format(b) for b in filelmza[:100])))
data = BytesIO(filelmza)
case _:
raise Exception("Invalid encoding")
@@ -515,10 +559,10 @@ class AnimX():
return True
@classmethod
def GetTrackType(cls, trackType2: int, value_type: type[resonite_types.ResoType], data: BytesIO) -> ResoTrack:
Track = TrackTypes[trackType2](value_type)
print(type(Track))
print(value_type)
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
+24 -13
View File
@@ -17,6 +17,19 @@ class ResoType():
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):
@@ -75,8 +88,8 @@ class byte(ResoType):
def __int__(self):
return self.x
def __init__(self):
pass
def __init__(self,value=0):
self.x = value
def write(self, data: BytesIO):
data.write(struct.pack("<B", self.x))
@@ -120,8 +133,8 @@ class short(ResoType):
def __int__(self):
return self.x
def __init__(self):
pass
def __init__(self,value=0):
self.x = value
def write(self, data: BytesIO):
data.write(struct.pack("<h", self.x))
@@ -264,7 +277,7 @@ class int4(int3):
pass
def read(self,data: BytesIO):
super().write(data)
super().read(data)
self.w = struct.unpack("<i", data.read(4))[0]
def write(self, data: BytesIO):
@@ -298,7 +311,7 @@ class uint2(uint):
pass
def read(self,data: BytesIO):
super().write(data)
super().read(data)
self.y = struct.unpack("<I", data.read(4))[0]
def write(self, data: BytesIO):
@@ -312,7 +325,7 @@ class uint3(uint2):
pass
def read(self,data: BytesIO):
super().write(data)
super().read(data)
self.z = struct.unpack("<I", data.read(4))[0]
def write(self, data: BytesIO):
@@ -326,7 +339,7 @@ class uint4(uint3):
pass
def read(self,data: BytesIO):
super().write(data)
super().read(data)
self.w = struct.unpack("<I", data.read(4))[0]
def write(self, data: BytesIO):
@@ -529,8 +542,7 @@ class doubleQ(double4):
def write(self, data: BytesIO):
super().write(data)
@classmethod
def read(cls, data: BytesIO):
def read(self, data: BytesIO):
super().read(data)
@@ -587,7 +599,7 @@ class float4(float3):
def __init__(self):
pass
def read(self,data: BytesIO):
def read(self, data: BytesIO):
super().read(data)
self.w = struct.unpack("<f", data.read(4))[0]
@@ -649,6 +661,5 @@ class floatQ(float4):
def write(self, data: BytesIO):
super().write(data)
@classmethod
def read(cls, data: BytesIO):
def read(self, data: BytesIO):
super().read(data)
+94 -34
View File
@@ -1,5 +1,7 @@
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
@@ -149,6 +151,14 @@ class AvatarToolKit_OT_ConvertToResonite(Operator):
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'
@@ -169,7 +179,7 @@ class AvatarToolKit_OT_AnimX_Importer(Operator,bpy_extras.io_utils.ImportHelper)
@classmethod
def poll(cls, context: Context) -> bool:
return True
return context.active_object != None
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:
files = [file.name for file in self.files]
files.append(self.filepath)
#files.append(self.filepath)
for file in files:
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_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:
print("hit here1")
print(track.FrameType)
if(track.FrameType != type(resonite_types.float) and track.FrameType != type(resonite_types.double)):
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
data_path: str = track._node.x+"."+track._property.x
fcurve_reso = action.fcurves.new(data_path,None,track._node.x)
print("hit here2")
match(type(track)):
case (type(resonite_animx.RawTrack)):
rawtrack: resonite_animx.RawTrack = track
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()
for frame in rawtrack.keyframes:
key: bpy.types.Keyframe = fcurve_reso.keyframe_points.insert(frame.time, float(frame.value))
case (resonite_animx.DiscreteTrack):
discretetrack: resonite_animx.DiscreteTrack = track
case (type(resonite_animx.DiscreteTrack)):
discretetrack: resonite_animx.RawTrack = track
for frame in rawtrack.keyframes:
key: bpy.types.Keyframe = fcurve_reso.keyframe_points.insert(frame.time, float(frame.value))
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
case(type(resonite_animx.CurveTrack)):
curvetrack: resonite_animx.RawTrack = track
for frame in curvetrack.keyframes:
key: bpy.types.Keyframe = fcurve_reso.keyframe_points.insert(frame.time, float(frame.value))
key.handle_left = float(frame.left_tan)
key.handle_left = float(frame.right_tan)
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(type(resonite_animx.BezierTrack)):
beziertrack: resonite_animx.RawTrack = track
# Bezier is not supported rn, ignore.
case(resonite_animx.BezierTrack):
beziertrack: resonite_animx.BezierTrack = track
# Bezier is not supported rn, ignore.
case _:
print("invalid track type, ignoring")
print(track)
return {'FINISHED'}