diff --git a/scripts/addons_core/io_scene_fbx/export_fbx_bin.py b/scripts/addons_core/io_scene_fbx/export_fbx_bin.py index 4641368ffb2..7f1e6bfea71 100644 --- a/scripts/addons_core/io_scene_fbx/export_fbx_bin.py +++ b/scripts/addons_core/io_scene_fbx/export_fbx_bin.py @@ -2491,16 +2491,25 @@ def fbx_animations(scene_data): # All actions. if scene_data.settings.bake_anim_use_all_actions: - def validate_actions(act, path_resolve): - for fc in act.fcurves: - data_path = fc.data_path - if fc.array_index: - data_path = data_path + "[%d]" % fc.array_index - try: - path_resolve(data_path) - except ValueError: - return False # Invalid. - return True # Valid. + def find_validate_action_slot(act, path_resolve) -> bpy.types.ActionSlot | None: + for layer in act.layers: + for strip in layer.strips: + for channelbag in strip.channelbags: + if not channelbag.fcurves: + # Do not export empty Channelbags. + continue + for fc in channelbag.fcurves: + data_path = fc.data_path + if fc.array_index: + data_path = data_path + "[%d]" % fc.array_index + try: + path_resolve(data_path) + except ValueError: + break # Invalid, go to next strip. + else: + # Did not 'break', so all F-Curves are valid. + return channelbag.slot + return None # Found nothing to return. def restore_object(ob_to, ob_from): # Restore org state of object (ugh :/ ). @@ -2540,14 +2549,20 @@ def fbx_animations(scene_data): pbones_matrices = [pbo.matrix_basis.copy() for pbo in ob.pose.bones] if ob.type == 'ARMATURE' else ... org_act = ob.animation_data.action + org_act_slot = ob.animation_data.action_slot path_resolve = ob.path_resolve for act in bpy.data.actions: # For now, *all* paths in the action must be valid for the object, to validate the action. # Unless that action was already assigned to the object! - if act != org_act and not validate_actions(act, path_resolve): + if act == org_act: + act_slot = org_act_slot + else: + act_slot = find_validate_action_slot(act, path_resolve) + if not act_slot: continue ob.animation_data.action = act + ob.animation_data.action_slot = act_slot frame_start, frame_end = act.frame_range # sic! add_anim(animations, animated, fbx_animations_do(scene_data, (ob, act), frame_start, frame_end, True, @@ -2557,6 +2572,7 @@ def fbx_animations(scene_data): for pbo, mat in zip(ob.pose.bones, pbones_matrices): pbo.matrix_basis = mat.copy() ob.animation_data.action = org_act + ob.animation_data.action_slot = org_act_slot restore_object(ob, ob_copy) scene.frame_set(scene.frame_current, subframe=0.0) @@ -2564,6 +2580,7 @@ def fbx_animations(scene_data): for pbo, mat in zip(ob.pose.bones, pbones_matrices): pbo.matrix_basis = mat.copy() ob.animation_data.action = org_act + ob.animation_data.action_slot = org_act_slot bpy.data.objects.remove(ob_copy) scene.frame_set(scene.frame_current, subframe=0.0) diff --git a/scripts/addons_core/io_scene_fbx/import_fbx.py b/scripts/addons_core/io_scene_fbx/import_fbx.py index e86a781527d..b48a2456e75 100644 --- a/scripts/addons_core/io_scene_fbx/import_fbx.py +++ b/scripts/addons_core/io_scene_fbx/import_fbx.py @@ -17,6 +17,7 @@ if "bpy" in locals(): import bpy from bpy.app.translations import pgettext_tip as tip_ from mathutils import Matrix, Euler, Vector, Quaternion +from bpy_extras import anim_utils # Also imported in .fbx_utils, so importing here is unlikely to further affect Blender startup time. import numpy as np @@ -893,10 +894,10 @@ def blen_store_keyframes_multi(fbx_key_times, fcurve_and_key_values_pairs, blen_ blen_fcurve.update() -def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, global_scale, shape_key_deforms, +def blen_read_animations_action_item(channelbag, item, cnodes, fps, anim_offset, global_scale, shape_key_deforms, fbx_ktime): """ - 'Bake' loc/rot/scale into the action, + 'Bake' loc/rot/scale into the channelbag, taking any pre_ and post_ matrix into account to transform from fbx into blender space. """ from bpy.types import ShapeKey, Material, Camera @@ -948,7 +949,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo else: # Euler props[1] = (bl_obj.path_from_id("rotation_euler"), 3, grpname or "Euler Rotation") - blen_curves = [action.fcurves.new(prop, index=channel, action_group=grpname) + blen_curves = [channelbag.fcurves.new(prop, index=channel, group_name=grpname) for prop, nbr_channels, grpname in props for channel in range(nbr_channels)] if isinstance(item, Material): @@ -1115,7 +1116,8 @@ def blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene, anim_o id_data.animation_data.action_slot = action.slots[0] # And actually populate the action! - blen_read_animations_action_item(action, item, cnodes, scene.render.fps, anim_offset, global_scale, + channelbag = anim_utils.action_ensure_channelbag_for_slot(action, action.slots[0]) + blen_read_animations_action_item(channelbag, item, cnodes, scene.render.fps, anim_offset, global_scale, shape_key_values, fbx_ktime) # If the minimum/maximum animated value is outside the slider range of the shape key, attempt to expand the slider