Skip to content

Commit bf0e7c8

Browse files
MysteryemMysteryem
authored and
Mysteryem
committed
FBX IO: Only import the first animation curve per channel
FBX's default animation system only uses the first animation curve assigned to a channel. This patch changes the FBX Importer to also only use the first animation curve assigned to a channel. Pull Request: https://projects.blender.org/blender/blender-addons/pulls/104866
1 parent 8b3bc24 commit bf0e7c8

File tree

2 files changed

+25
-50
lines changed

2 files changed

+25
-50
lines changed

io_scene_fbx/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
bl_info = {
66
"name": "FBX format",
77
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
8-
"version": (5, 7, 1),
8+
"version": (5, 7, 2),
99
"blender": (3, 6, 0),
1010
"location": "File > Import-Export",
1111
"description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",

io_scene_fbx/import_fbx.py

+24-49
Original file line numberDiff line numberDiff line change
@@ -626,39 +626,6 @@ def _transformation_curves_gen(item, values_arrays, channel_keys):
626626
yield from sca
627627

628628

629-
def blen_read_animation_channel_curves(curves):
630-
"""Read one or (very rarely) more animation curves, that affect a single channel of a single property, from FBX
631-
data.
632-
633-
When there are multiple curves, they will be combined into a single sorted animation curve with later curves taking
634-
precedence when the curves contain duplicate times.
635-
636-
It is expected that there will almost never be more than a single curve to read because FBX's default animation
637-
system only uses the first curve assigned to a channel.
638-
639-
Returns an array of sorted, unique FBX keyframe times and an array of values for each of those keyframe times."""
640-
if len(curves) > 1:
641-
times_and_values_tuples = list(map(blen_read_single_animation_curve, curves))
642-
# The FBX animation system's default implementation only uses the first curve assigned to a channel.
643-
# Additional curves per channel are allowed by the FBX specification, but the handling of these curves is
644-
# considered the responsibility of the application that created them. Note that each curve node is expected to
645-
# have a unique set of channels, so these additional curves with the same channel would have to belong to
646-
# separate curve nodes. See the FBX SDK documentation for FbxAnimCurveNode.
647-
648-
# Combine the curves together to produce a single array of sorted keyframe times and a single array of values.
649-
# The arrays are concatenated in reverse so that if there are duplicate times in the read curves, then only the
650-
# value of the last occurrence is kept.
651-
all_times = np.concatenate([t[0] for t in reversed(times_and_values_tuples)])
652-
all_values = np.concatenate([t[1] for t in reversed(times_and_values_tuples)])
653-
# Get the unique, sorted times and the index in all_times of the first occurrence of each unique value.
654-
sorted_unique_times, unique_indices_in_all_times = np.unique(all_times, return_index=True)
655-
656-
values_of_sorted_unique_times = all_values[unique_indices_in_all_times]
657-
return sorted_unique_times, values_of_sorted_unique_times
658-
else:
659-
return blen_read_single_animation_curve(curves[0])
660-
661-
662629
def _combine_curve_keyframe_times(times_and_values_tuples, initial_values):
663630
"""Combine multiple parsed animation curves, that affect different channels, such that every animation curve
664631
contains the keyframes from every other curve, interpolating the values for the newly inserted keyframes in each
@@ -779,8 +746,8 @@ def _convert_fbx_time_to_blender_time(key_times, blen_start_offset, fbx_start_of
779746
return key_times
780747

781748

782-
def blen_read_single_animation_curve(fbx_curve):
783-
"""Read a single animation curve from FBX data.
749+
def blen_read_animation_curve(fbx_curve):
750+
"""Read an animation curve from FBX data.
784751
785752
The parsed keyframe times are guaranteed to be strictly increasing."""
786753
key_times = parray_as_ndarray(elem_prop_first(elem_find_first(fbx_curve, b'KeyTime')))
@@ -851,11 +818,19 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
851818
"""
852819
from bpy.types import Object, PoseBone, ShapeKey, Material, Camera
853820

854-
fbx_curves: dict[bytes, dict[int, list[FBXElem]]] = {}
821+
fbx_curves: dict[bytes, dict[int, FBXElem]] = {}
855822
for curves, fbxprop in cnodes.values():
856823
channels_dict = fbx_curves.setdefault(fbxprop, {})
857824
for (fbx_acdata, _blen_data), channel in curves.values():
858-
channels_dict.setdefault(channel, []).append(fbx_acdata)
825+
if channel in channels_dict:
826+
# Ignore extra curves when one has already been found for this channel because FBX's default animation
827+
# system implementation only uses the first curve assigned to a channel.
828+
# Additional curves per channel are allowed by the FBX specification, but the handling of these curves
829+
# is considered the responsibility of the application that created them. Note that each curve node is
830+
# expected to have a unique set of channels, so these additional curves with the same channel would have
831+
# to belong to separate curve nodes. See the FBX SDK documentation for FbxAnimCurveNode.
832+
continue
833+
channels_dict[channel] = fbx_acdata
859834

860835
# Leave if no curves are attached (if a blender curve is attached to scale but without keys it defaults to 0).
861836
if len(fbx_curves) == 0:
@@ -894,23 +869,23 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
894869
for prop, nbr_channels, grpname in props for channel in range(nbr_channels)]
895870

896871
if isinstance(item, Material):
897-
for fbxprop, channel_to_curves in fbx_curves.items():
872+
for fbxprop, channel_to_curve in fbx_curves.items():
898873
assert(fbxprop == b'DiffuseColor')
899-
for channel, curves in channel_to_curves.items():
874+
for channel, curve in channel_to_curve.items():
900875
assert(channel in {0, 1, 2})
901876
blen_curve = blen_curves[channel]
902-
fbx_key_times, values = blen_read_animation_channel_curves(curves)
877+
fbx_key_times, values = blen_read_animation_curve(curve)
903878
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
904879

905880
elif isinstance(item, ShapeKey):
906881
deform_values = shape_key_deforms.setdefault(item, [])
907-
for fbxprop, channel_to_curves in fbx_curves.items():
882+
for fbxprop, channel_to_curve in fbx_curves.items():
908883
assert(fbxprop == b'DeformPercent')
909-
for channel, curves in channel_to_curves.items():
884+
for channel, curve in channel_to_curve.items():
910885
assert(channel == 0)
911886
blen_curve = blen_curves[channel]
912887

913-
fbx_key_times, values = blen_read_animation_channel_curves(curves)
888+
fbx_key_times, values = blen_read_animation_curve(curve)
914889
# A fully activated shape key in FBX DeformPercent is 100.0 whereas it is 1.0 in Blender.
915890
values = values / 100.0
916891
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
@@ -921,15 +896,15 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
921896
deform_values.append(values.max())
922897

923898
elif isinstance(item, Camera):
924-
for fbxprop, channel_to_curves in fbx_curves.items():
899+
for fbxprop, channel_to_curve in fbx_curves.items():
925900
is_focus_distance = fbxprop == b'FocusDistance'
926901
assert(fbxprop == b'FocalLength' or is_focus_distance)
927-
for channel, curves in channel_to_curves.items():
902+
for channel, curve in channel_to_curve.items():
928903
assert(channel == 0)
929904
# The indices are determined by the creation of the `props` list above.
930905
blen_curve = blen_curves[1 if is_focus_distance else 0]
931906

932-
fbx_key_times, values = blen_read_animation_channel_curves(curves)
907+
fbx_key_times, values = blen_read_animation_curve(curve)
933908
if is_focus_distance:
934909
# Remap the imported values from FBX to Blender.
935910
values = values / 1000.0
@@ -950,13 +925,13 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
950925
times_and_values_tuples = []
951926
initial_values = []
952927
channel_keys = []
953-
for fbxprop, channel_to_curves in fbx_curves.items():
928+
for fbxprop, channel_to_curve in fbx_curves.items():
954929
if fbxprop not in transform_prop_to_attr:
955930
# Currently, we only care about transformation curves.
956931
continue
957-
for channel, curves in channel_to_curves.items():
932+
for channel, curve in channel_to_curve.items():
958933
assert(channel in {0, 1, 2})
959-
fbx_key_times, values = blen_read_animation_channel_curves(curves)
934+
fbx_key_times, values = blen_read_animation_curve(curve)
960935

961936
channel_keys.append((fbxprop, channel))
962937

0 commit comments

Comments
 (0)