From 2337449a77f66b26ac09e769fac6a25ced1f33b3 Mon Sep 17 00:00:00 2001 From: 989onan Date: Sun, 15 Dec 2024 16:09:18 -0500 Subject: [PATCH] AnimX importer done Finished the importer for now, it may contain errors but those would be part of the library --- core/common.py | 49 ----- core/preferences.json | 2 +- core/resonite_loader/common.py | 19 +- core/resonite_loader/resonite_animx.py | 264 ++++++++++++++----------- core/resonite_loader/resonite_types.py | 37 ++-- core/resonite_utils.py | 128 ++++++++---- 6 files changed, 287 insertions(+), 212 deletions(-) diff --git a/core/common.py b/core/common.py index bc06b41..a1f9e7b 100644 --- a/core/common.py +++ b/core/common.py @@ -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(' 0) - num |= ((b & 127) << num2) - num2 += 7 - if not flag: - break - - return num - -def read7bitEncoded_int(data: BytesIO) -> ctypes.c_int: - num: ctypes.c_int = ctypes.c_int(0) - num2:ctypes.c_int = ctypes.c_int(0) - while (num2 != 35): - b: ctypes.c_ubyte = ctypes.c_ubyte(struct.unpack(' None: - while integer > ctypes.c_ulong(0): - b: ctypes.c_ubyte = ctypes.c_ubyte(integer & ctypes.c_ulong(127)) - integer >>= 7 - if integer > ctypes.c_ulong(0): - b |= 128 - data.write(b) - if integer <= ctypes.c_ulong(0): - return - -def write7bitEncoded_int(data: BytesIO, value: ctypes.c_int) -> None: - num: ctypes.c_uint = ctypes.c_uint(value) - while(num >= ctypes.c_ubyte(128)): - data.write(ctypes.c_ubyte(num | ctypes.c_ubyte(128))) - num >>= 7 - data.Write(ctypes.c_ubyte(num)) #encoding FrooxEngine/C# types in binary: diff --git a/core/preferences.json b/core/preferences.json index 4fd9b5f..754f04a 100644 --- a/core/preferences.json +++ b/core/preferences.json @@ -1,4 +1,4 @@ { "language": 0, - "last_update_check": 1734208835.8049936 + "last_update_check": 1734295375.2681296 } \ No newline at end of file diff --git a/core/resonite_loader/common.py b/core/resonite_loader/common.py index f42e2e8..49ad0d9 100644 --- a/core/resonite_loader/common.py +++ b/core/resonite_loader/common.py @@ -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(' 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)) \ No newline at end of file + data.Write(int(num)) diff --git a/core/resonite_loader/resonite_animx.py b/core/resonite_loader/resonite_animx.py index 3bb8bb1..ad6c4cf 100644 --- a/core/resonite_loader/resonite_animx.py +++ b/core/resonite_loader/resonite_animx.py @@ -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(" 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 diff --git a/core/resonite_loader/resonite_types.py b/core/resonite_loader/resonite_types.py index e1052a7..dbdaf7f 100644 --- a/core/resonite_loader/resonite_types.py +++ b/core/resonite_loader/resonite_types.py @@ -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(" 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'}