diff --git a/leenkx/blender/lnx/exporter.py b/leenkx/blender/lnx/exporter.py index 0496f233..6187c6cc 100644 --- a/leenkx/blender/lnx/exporter.py +++ b/leenkx/blender/lnx/exporter.py @@ -276,10 +276,22 @@ class LeenkxExporter: if armature.animation_data: action = armature.animation_data.action if action: - return [fcurve for fcurve in action.fcurves if fcurve.data_path.startswith(path)] + fcurves = LeenkxExporter.get_action_fcurves(action, armature.animation_data) + return [fcurve for fcurve in fcurves if fcurve.data_path.startswith(path)] return [] + @staticmethod + def get_action_fcurves(action: bpy.types.Action, anim_data: Optional[bpy.types.AnimData] = None) -> List[bpy.types.FCurve]: + if bpy.app.version >= (5, 0, 0): + if anim_data and anim_data.action_slot: + from bpy_extras import anim_utils + channelbag = anim_utils.action_ensure_channelbag_for_slot(action, anim_data.action_slot) + return channelbag.fcurves if channelbag else [] + return [] + else: + return action.fcurves + def export_bone(self, armature, bone: bpy.types.Bone, o, action: bpy.types.Action): rpdat = lnx.utils.get_rp() bobject_ref = self.bobject_bone_array.get(bone) @@ -319,7 +331,7 @@ class LeenkxExporter: oanim['root_motion_rot'] = action.lnx_root_motion_rot @staticmethod - def calculate_anim_frame_range(action: bpy.types.Action) -> Tuple[int, int]: + def calculate_anim_frame_range(action: bpy.types.Action, anim_data: Optional[bpy.types.AnimData] = None) -> Tuple[int, int]: """Calculates the required frame range of the given action by also taking fcurve modifiers into account. @@ -330,14 +342,16 @@ class LeenkxExporter: start = frame_range[0] end = frame_range[1] + fcurves = LeenkxExporter.get_action_fcurves(action, anim_data) + # Blender 4.0+ compatibility: Handle zero-length frame ranges if start == end: start = 1 end = 2 - if action.fcurves: + if fcurves: all_keyframes = [] - for fcurve in action.fcurves: + for fcurve in fcurves: if fcurve.keyframe_points: for keyframe in fcurve.keyframe_points: all_keyframes.append(keyframe.co[0]) @@ -350,7 +364,7 @@ class LeenkxExporter: # Take FCurve modifiers into account if they have a restricted # frame range - for fcurve in action.fcurves: + for fcurve in fcurves: for modifier in fcurve.modifiers: if not modifier.use_restricted_range: continue @@ -404,7 +418,7 @@ class LeenkxExporter: o['object_actions'] = [] o['object_actions'].append('action_' + action_name + ext) - frame_range = self.calculate_anim_frame_range(action) + frame_range = self.calculate_anim_frame_range(action, bobject.animation_data) out_anim = { 'begin': frame_range[0], 'end': frame_range[1], @@ -414,7 +428,8 @@ class LeenkxExporter: self.export_pose_markers(out_anim, action) unresolved_data_paths = set() - for fcurve in action.fcurves: + fcurves = self.get_action_fcurves(action, bobject.animation_data) + for fcurve in fcurves: data_path = fcurve.data_path try: @@ -535,7 +550,7 @@ class LeenkxExporter: fcurve_list = self.collect_bone_animation(armature, bone.name) if fcurve_list and pose_bone: - begin_frame, end_frame = self.calculate_anim_frame_range(action) + begin_frame, end_frame = self.calculate_anim_frame_range(action, armature.animation_data) out_track = {'target': "transform", 'frames': [], 'values': []} o['anim'] = {'tracks': [out_track]} @@ -653,8 +668,8 @@ class LeenkxExporter: return fov return None - def write_bone_matrices(self, scene, action): - begin_frame, end_frame = self.calculate_anim_frame_range(action) + def write_bone_matrices(self, scene, action, anim_data: Optional[bpy.types.AnimData] = None): + begin_frame, end_frame = self.calculate_anim_frame_range(action, anim_data) if len(self.bone_tracks) > 0: hidden_states = {} try: @@ -807,7 +822,8 @@ class LeenkxExporter: # if (shapeKeys.animation_data): # action = shapeKeys.animation_data.action # if (action): - # for fcurve in action.fcurves: + # fcurves = self.get_action_fcurves(action, shapeKeys.animation_data) + # for fcurve in fcurves: # if ((fcurve.data_path.startswith("key_blocks[")) and (fcurve.data_path.endswith("].value"))): # keyName = fcurve.data_path.strip("abcdehklopstuvy[]_.") # if ((keyName[0] == "\"") or (keyName[0] == "'")): @@ -822,7 +838,8 @@ class LeenkxExporter: # if ((not action) and (node.animation_data)): # action = node.animation_data.action # if (action): - # for fcurve in action.fcurves: + # fcurves = self.get_action_fcurves(action, node.animation_data) + # for fcurve in fcurves: # if ((fcurve.data_path.startswith("data.shape_keys.key_blocks[")) and (fcurve.data_path.endswith("].value"))): # keyName = fcurve.data_path.strip("abcdehklopstuvy[]_.") # if ((keyName[0] == "\"") or (keyName[0] == "'")): @@ -1324,7 +1341,7 @@ class LeenkxExporter: _bake_hidden[_obj] = False _obj.hide_viewport = True - start, end = self.calculate_anim_frame_range(action) + start, end = self.calculate_anim_frame_range(action, skelobj.animation_data) bake_result = bpy.ops.nla.bake( frame_start=start, @@ -1361,7 +1378,7 @@ class LeenkxExporter: boneo = {} self.export_bone(skelobj, bone, boneo, action) bones.append(boneo) - self.write_bone_matrices(bpy.context.scene, action) + self.write_bone_matrices(bpy.context.scene, action, skelobj.animation_data) if len(bones) > 0 and 'anim' in bones[0]: self.export_pose_markers(bones[0]['anim'], original_action) self.export_root_motion(bones[0]['anim'], original_action)