FBX IO: Speed up animation import using NumPy #104856
@ -530,7 +530,7 @@ def _transformation_curves_gen(item, values_arrays, channel_keys):
|
||||
"""Yields flattened location/rotation/scaling values for imported PoseBone/Object Lcl Translation/Rotation/Scaling
|
||||
animation curve values.
|
||||
|
||||
The value arrays must have the same lengths where each index of each array corresponds to a single keyframe.
|
||||
The value arrays must have the same lengths, where each index of each array corresponds to a single keyframe.
|
||||
|
||||
Each value array must have a corresponding channel key tuple that identifies the fbx property
|
||||
(b'Lcl Translation'/b'Lcl Rotation'/b'Lcl Scaling') and the channel (x/y/z as 0/1/2) of that property."""
|
||||
@ -561,7 +561,7 @@ def _transformation_curves_gen(item, values_arrays, channel_keys):
|
||||
setters = [partial(setitem, transform_prop_to_attr[fbx_prop], channel) for fbx_prop, channel in channel_keys]
|
||||
frame_values_it = zip(*(iter(arr.data) for arr in values_arrays))
|
||||
|
||||
# Pre-get/calculate these to reduce the work done inside the hot loop.
|
||||
# Pre-get/calculate these to slightly reduce the work done inside the loop.
|
||||
anim_compensation_matrix = item.anim_compensation_matrix
|
||||
do_anim_compensation_matrix = bool(anim_compensation_matrix)
|
||||
|
||||
@ -602,7 +602,7 @@ def _transformation_curves_gen(item, values_arrays, channel_keys):
|
||||
if do_restmat_inv:
|
||||
mat = restmat_inv @ mat
|
||||
|
||||
# Now we have a virtual matrix of transform from AnimCurves, we can insert keyframes!
|
||||
# Now we have a virtual matrix of transform from AnimCurves, we can yield keyframe values!
|
||||
loc, rot, sca = decompose(mat)
|
||||
if rot_mode == 'QUATERNION':
|
||||
if rot_quat_prev.dot(rot) < 0.0:
|
||||
@ -622,12 +622,13 @@ def _transformation_curves_gen(item, values_arrays, channel_keys):
|
||||
|
||||
|
||||
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.
|
||||
"""Read one or (very rarely) more animation curves, that affect a single same channel of a single 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.
|
||||
It is expected that there will almost never be more than a single curve to read because FBX's default animation
|
||||
system only uses the first curve assigned to a channel.
|
||||
|
||||
Returns an array of sorted, unique FBX keyframe times and an array of values for each of those keyframe times."""
|
||||
if len(curves) > 1:
|
||||
@ -652,7 +653,7 @@ def blen_read_animation_channel_curves(curves):
|
||||
return blen_read_single_animation_curve(curves[0])
|
||||
|
||||
|
||||
def _combine_curve_keyframes(times_and_values_tuples, initial_values):
|
||||
def _combine_curve_keyframe_times(times_and_values_tuples, initial_values):
|
||||
"""Combine multiple sorted animation curves, that affect different properties, such that every animation curve
|
||||
contains the keyframes from every other curve, interpolating the values for the newly inserted keyframes in each
|
||||
curve.
|
||||
@ -711,14 +712,13 @@ def blen_read_invalid_animation_curve(key_times, key_values):
|
||||
indexed_times = key_times[indices]
|
||||
indexed_values = key_values[indices]
|
||||
|
||||
# Interpolate the value for each time in sorted_unique_times according to the times and values at each index and
|
||||
# the previous index.
|
||||
# Interpolate the value for each time in sorted_unique_times according to the times and values at each index and the
|
||||
# previous index.
|
||||
interpolated_values = np.empty_like(indexed_values)
|
||||
|
||||
# Where the index is 0, there's no previous value to interpolate from, so we set the value without
|
||||
# interpolating.
|
||||
# Because the indices are in increasing order, all zeroes must be at the start, so we can find the index of the
|
||||
# last zero and use that to index with a slice instead of a boolean array for performance.
|
||||
# Where the index is 0, there's no previous value to interpolate from, so we set the value without interpolating.
|
||||
# Because the indices are in increasing order, all zeroes must be at the start, so we can find the index of the last
|
||||
# zero and use that to index with a slice instead of a boolean array for performance.
|
||||
# Equivalent to, but as a slice:
|
||||
# idx_zero_mask = indices == 0
|
||||
# idx_nonzero_mask = ~idx_zero_mask
|
||||
@ -757,7 +757,6 @@ def blen_read_invalid_animation_curve(key_times, key_values):
|
||||
|
||||
|
||||
def _convert_fbx_time_to_blender_time(key_times, blen_start_offset, fbx_start_offset, fps):
|
||||
# todo: Could move this into blen_store_keyframes since it probably doesn't need to be used anywhere else
|
||||
from .fbx_utils import FBX_KTIME
|
||||
timefac = fps / FBX_KTIME
|
||||
|
||||
@ -788,8 +787,6 @@ def blen_read_single_animation_curve(fbx_curve):
|
||||
if all_times_strictly_increasing:
|
||||
return key_times, key_values
|
||||
else:
|
||||
# todo: Print something to the console warning that the animation curve was invalid.
|
||||
|
||||
# FBX will still read animation curves even if they are invalid.
|
||||
return blen_read_invalid_animation_curve(key_times, key_values)
|
||||
|
||||
@ -815,8 +812,10 @@ def blen_store_keyframes_multi(fbx_key_times, fcurve_and_key_values_pairs, blen_
|
||||
bl_enum_dtype = np.byte
|
||||
|
||||
# The keyframe_points 'co' are accessed as flattened pairs of (time, value).
|
||||
# The key times are the same for each (blen_fcurve, key_values) pair, so only the values need to be updatedfor each array of values.
|
||||
# The key times are the same for each (blen_fcurve, key_values) pair, so only the values need to be updated for each
|
||||
# array of values.
|
||||
keyframe_points_co = np.empty(len(bl_key_times) * 2, dtype=bl_keyframe_dtype)
|
||||
# Even indices are times.
|
||||
keyframe_points_co[0::2] = bl_key_times
|
||||
|
||||
interpolation_array = np.full(num_keys, LINEAR_INTERPOLATION_VALUE, dtype=bl_enum_dtype)
|
||||
@ -824,6 +823,8 @@ def blen_store_keyframes_multi(fbx_key_times, fcurve_and_key_values_pairs, blen_
|
||||
for blen_fcurve, key_values in fcurve_and_key_values_pairs:
|
||||
# The fcurve must be newly created and thus have no keyframe_points.
|
||||
assert(len(blen_fcurve.keyframe_points) == 0)
|
||||
|
||||
# Odd indices are values.
|
||||
keyframe_points_co[1::2] = key_values
|
||||
|
||||
# Add the keyframe points to the FCurve and then set the 'co' and 'interpolation' of each point.
|
||||
@ -906,8 +907,8 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
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.
|
||||
# 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())
|
||||
deform_values.append(values.max())
|
||||
|
||||
@ -956,12 +957,16 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
times_and_values_tuples.append((fbx_key_times, values))
|
||||
if not times_and_values_tuples:
|
||||
# If `times_and_values_tuples` is empty, all the imported animation curves are for properties other than
|
||||
# transformation (e.g. animated custom properties), so there is nothing to do until support for these other
|
||||
# transformation (e.g. animated custom properties), so there is nothing to do until support for those other
|
||||
# properties is added.
|
||||
return
|
||||
|
||||
combined_fbx_times, values_arrays = _combine_curve_keyframes(times_and_values_tuples, initial_values)
|
||||
# Combine the keyframe times of all the transformation curves so that each curve has a value at every time.
|
||||
combined_fbx_times, values_arrays = _combine_curve_keyframe_times(times_and_values_tuples, initial_values)
|
||||
|
||||
# Convert from FBX Lcl Translation/Lcl Rotation/Lcl Scaling to the Blender location/rotation/scaling properties
|
||||
# of this Object/PoseBone.
|
||||
# The number of fcurves for the Blender properties varies depending on the rotation mode.
|
||||
num_loc_channels = 3
|
||||
num_rot_channels = 4 if rot_mode in {'QUATERNION', 'AXIS_ANGLE'} else 3 # Variations of EULER are all 3
|
||||
num_sca_channels = 3
|
||||
@ -969,8 +974,10 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
num_frames = len(combined_fbx_times)
|
||||
full_length = num_channels * num_frames
|
||||
|
||||
# Do the conversion.
|
||||
flattened_channel_values_gen = _transformation_curves_gen(item, values_arrays, channel_keys)
|
||||
flattened_channel_values = np.fromiter(flattened_channel_values_gen, dtype=np.single, count=full_length)
|
||||
|
||||
# Reshape to one row per frame and then view the transpose so that each row corresponds to a single channel.
|
||||
# e.g.
|
||||
# loc_channels = channel_values[:num_loc_channels]
|
||||
@ -978,6 +985,8 @@ def blen_read_animations_action_item(action, item, cnodes, fps, anim_offset, glo
|
||||
# sca_channels = channel_values[num_loc_channels + num_rot_channels:]
|
||||
channel_values = flattened_channel_values.reshape(num_frames, num_channels).T
|
||||
|
||||
# Each channel has the same keyframe times, so the combined times can be passed once along with all the curves
|
||||
# and values arrays.
|
||||
blen_store_keyframes_multi(combined_fbx_times, zip(blen_curves, channel_values), anim_offset, fps)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user