FBX IO: Speed up animation import using NumPy #104856
@ -621,33 +621,35 @@ def _transformation_curves_gen(item, values_arrays, channel_keys):
|
|||||||
yield from sca
|
yield from sca
|
||||||
|
|
||||||
|
|
||||||
def _combine_same_property_curves(times_and_values_tuples):
|
def blen_read_animation_channel_curves(curves):
|
||||||
"""Combine multiple sorted animation curves, that affect the same property, into a single sorted animation curve."""
|
"""Read one or (rarely) more animation curves, that affect the same channel of the same property, from FBX data.
|
||||||
if len(times_and_values_tuples) > 1:
|
|
||||||
# TODO: Print a warning to the console that more than one curve was found
|
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.
|
# 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
|
# 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
|
# 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
|
# 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.
|
# separate curve nodes. See the FBX SDK documentation for FbxAnimCurveNode.
|
||||||
|
|
||||||
# Concatenate all the times into one array and all the values into one array.
|
# Combine the curves together to produce a single array of sorted keyframe times and a single array of values.
|
||||||
all_times = np.concatenate([t[0] for t in times_and_values_tuples])
|
# The arrays are concatenated in reverse so that if there are duplicate times in the read curves, then only the
|
||||||
all_values = np.concatenate([t[1] for t in times_and_values_tuples])
|
# 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.
|
# 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)
|
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]
|
values_of_sorted_unique_times = all_values[unique_indices_in_all_times]
|
||||||
return sorted_unique_times, values_of_sorted_unique_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:
|
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):
|
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():
|
for channel, curves in channel_to_curves.items():
|
||||||
assert(channel in {0, 1, 2})
|
assert(channel in {0, 1, 2})
|
||||||
blen_curve = blen_curves[channel]
|
blen_curve = blen_curves[channel]
|
||||||
|
fbx_key_times, values = blen_read_animation_channel_curves(curves)
|
||||||
parsed_curves = tuple(map(blen_read_single_animation_curve, curves))
|
|
||||||
fbx_key_times, values = _combine_same_property_curves(parsed_curves)
|
|
||||||
|
|
||||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
|
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
|
||||||
|
|
||||||
elif isinstance(item, ShapeKey):
|
elif isinstance(item, ShapeKey):
|
||||||
@ -902,12 +901,11 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
|||||||
assert(channel == 0)
|
assert(channel == 0)
|
||||||
blen_curve = blen_curves[channel]
|
blen_curve = blen_curves[channel]
|
||||||
|
|
||||||
parsed_curves = tuple(map(blen_read_single_animation_curve, curves))
|
fbx_key_times, values = blen_read_animation_channel_curves(curves)
|
||||||
fbx_key_times, values = _combine_same_property_curves(parsed_curves)
|
|
||||||
# A fully activated shape key in FBX DeformPercent is 100.0 whereas it is 1.0 in Blender.
|
# A fully activated shape key in FBX DeformPercent is 100.0 whereas it is 1.0 in Blender.
|
||||||
values = values / 100.0
|
values = values / 100.0
|
||||||
|
|
||||||
blen_store_keyframes(fbx_key_times, blen_curve, values, anim_offset, fps)
|
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
|
# 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.
|
# necessary after reading all animations.
|
||||||
deform_values.append(values.min())
|
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.
|
# The indices are determined by the creation of the `props` list above.
|
||||||
blen_curve = blen_curves[1 if is_focus_distance else 0]
|
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 = blen_read_animation_channel_curves(curves)
|
||||||
fbx_key_times, values = _combine_same_property_curves(parsed_curves)
|
|
||||||
if is_focus_distance:
|
if is_focus_distance:
|
||||||
# Remap the imported values from FBX to Blender.
|
# Remap the imported values from FBX to Blender.
|
||||||
values = values / 1000.0
|
values = values / 1000.0
|
||||||
@ -950,8 +947,7 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
|||||||
continue
|
continue
|
||||||
for channel, curves in channel_to_curves.items():
|
for channel, curves in channel_to_curves.items():
|
||||||
assert(channel in {0, 1, 2})
|
assert(channel in {0, 1, 2})
|
||||||
parsed_curves = tuple(map(blen_read_single_animation_curve, curves))
|
fbx_key_times, values = blen_read_animation_channel_curves(curves)
|
||||||
fbx_key_times, values = _combine_same_property_curves(parsed_curves)
|
|
||||||
|
|
||||||
channel_keys.append((fbxprop, channel))
|
channel_keys.append((fbxprop, channel))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user