FBX IO: Speed up animation import using NumPy #104856

Merged
Thomas Barlow merged 12 commits from Mysteryem/blender-addons:fbx_import_anim_numpy_p1 into main 2023-09-04 22:07:45 +02:00
Showing only changes of commit d83d71e45b - Show all commits

View File

@ -621,33 +621,35 @@ def _transformation_curves_gen(item, values_arrays, channel_keys):
yield from sca
def _combine_same_property_curves(times_and_values_tuples):
"""Combine multiple sorted animation curves, that affect the same property, into a single sorted animation curve."""
if len(times_and_values_tuples) > 1:
# TODO: Print a warning to the console that more than one curve was found
def blen_read_animation_channel_curves(curves):
"""Read one or (rarely) more animation curves, that affect the same channel of the same property, from FBX data.
When there are multiple curves, they will be combined into a single sorted animation curve.
Though, it is expected that there will almost never be more than a single curve to read because multiple curves
affecting the same channel of the same property is not part of FBX's default animation system.
Returns an array of sorted, unique FBX keyframe times and an array of values for each of those keyframe times."""
if len(curves) > 1:
times_and_values_tuples = list(map(blen_read_single_animation_curve, curves))
# The FBX animation system's default implementation only uses the first curve assigned to a channel.
# Additional curves per channel are allowed by the FBX specification, but the handling of these curves is
# considered the responsibility of the application that created them. Note that each curve node is expected to
# have a unique set of channels, so these additional curves with the same channel would have to belong to
# separate curve nodes. See the FBX SDK documentation for FbxAnimCurveNode.
# Concatenate all the times into one array and all the values into one array.
all_times = np.concatenate([t[0] for t in times_and_values_tuples])
all_values = np.concatenate([t[1] for t in times_and_values_tuples])
# Combine the curves together to produce a single array of sorted keyframe times and a single array of values.
# The arrays are concatenated in reverse so that if there are duplicate times in the read curves, then only the
# value of the last occurrence is kept.
all_times = np.concatenate([t[0] for t in reversed(times_and_values_tuples)])
all_values = np.concatenate([t[1] for t in reversed(times_and_values_tuples)])
# Get the unique, sorted times and the index in all_times of the first occurrence of each unique value.
sorted_unique_times, unique_indices_in_all_times = np.unique(all_times, return_index=True)
values_of_sorted_unique_times = all_values[unique_indices_in_all_times]
return sorted_unique_times, values_of_sorted_unique_times
# # Get the indices that would sort all_times.
# # Use a stable algorithm so that if there are any duplicate times, they maintain their original order.
# perm = np.argsort(kind='stable')
# # Use the indices to sort both all_times and all_values.
# all_times = all_times[perm]
# all_values = all_values[perm]
else:
return times_and_values_tuples[0]
return blen_read_single_animation_curve(curves[0])
def _combine_curve_keyframes(times_and_values_tuples, initial_values):
@ -888,10 +890,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
for channel, curves in channel_to_curves.items():
assert(channel in {0, 1, 2})
blen_curve = blen_curves[channel]
parsed_curves = tuple(map(blen_read_single_animation_curve, curves))
fbx_key_times, values = _combine_same_property_curves(parsed_curves)
fbx_key_times, values = blen_read_animation_channel_curves(curves)
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
elif isinstance(item, ShapeKey):
@ -902,12 +901,11 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
assert(channel == 0)
blen_curve = blen_curves[channel]
parsed_curves = tuple(map(blen_read_single_animation_curve, curves))
fbx_key_times, values = _combine_same_property_curves(parsed_curves)
fbx_key_times, values = blen_read_animation_channel_curves(curves)
# A fully activated shape key in FBX DeformPercent is 100.0 whereas it is 1.0 in Blender.
values = values / 100.0
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
# Store the minimum and maximum shape key values, so that the shape key's slider range can be expanded if
# necessary after reading all animations.
deform_values.append(values.min())
@ -922,8 +920,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
# The indices are determined by the creation of the `props` list above.
blen_curve = blen_curves[1 if is_focus_distance else 0]
parsed_curves = tuple(map(blen_read_single_animation_curve, curves))
fbx_key_times, values = _combine_same_property_curves(parsed_curves)
fbx_key_times, values = blen_read_animation_channel_curves(curves)
if is_focus_distance:
# Remap the imported values from FBX to Blender.
values = values / 1000.0
@ -950,8 +947,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
continue
for channel, curves in channel_to_curves.items():
assert(channel in {0, 1, 2})
parsed_curves = tuple(map(blen_read_single_animation_curve, curves))
fbx_key_times, values = _combine_same_property_curves(parsed_curves)
fbx_key_times, values = blen_read_animation_channel_curves(curves)
channel_keys.append((fbxprop, channel))