FBX IO: Speed up animation simplification using NumPy #104904

Merged
Thomas Barlow merged 17 commits from Mysteryem/blender-addons:fbx_anim_export_numpy_simplify into blender-v4.0-release 2023-10-06 17:53:02 +02:00
2 changed files with 61 additions and 56 deletions
Showing only changes of commit 4232afbc6a - Show all commits

View File

@ -2255,6 +2255,8 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
has_animated_duplis = bool(dupli_parent_bdata) has_animated_duplis = bool(dupli_parent_bdata)
# Initialize keyframe times array. Each AnimationCurveNodeWrapper will share the same instance. # Initialize keyframe times array. Each AnimationCurveNodeWrapper will share the same instance.
# `np.arange` excludes the `stop` argument like when using `range`, so we use np.nextafter to get the next
# representable value after f_end and use that as the `stop` argument instead.
currframes = np.arange(f_start, np.nextafter(f_end, np.inf), step=bake_step) currframes = np.arange(f_start, np.nextafter(f_end, np.inf), step=bake_step)
real_currframes = currframes - f_start if start_zero else currframes real_currframes = currframes - f_start if start_zero else currframes

View File

@ -1283,9 +1283,9 @@ class AnimationCurveNodeWrapper:
def set_keyframes(self, keyframe_times, keyframe_values): def set_keyframes(self, keyframe_times, keyframe_values):
""" """
Set all keyframe times and values of the group. Set all keyframe times and values of the group.
Values can be a 2D array where each row is a separate curve. Values can be a 2D array where each row is the values for a separate curve.
""" """
# View 1D keyframe_values as 2D with a single row, so that the same iterative code can be used for both 1D and # View 1D keyframe_values as 2D with a single row, so that the same code can be used for both 1D and
# 2D inputs. # 2D inputs.
if len(keyframe_values.shape) == 1: if len(keyframe_values.shape) == 1:
keyframe_values = keyframe_values[np.newaxis] keyframe_values = keyframe_values[np.newaxis]
@ -1304,6 +1304,7 @@ class AnimationCurveNodeWrapper:
* their values relatively differ from the previous sample ones. * their values relatively differ from the previous sample ones.
""" """
if self._frame_times_array is None: if self._frame_times_array is None:
# Keyframes have not been added yet.
return return
if fac == 0.0: if fac == 0.0:
@ -1313,18 +1314,19 @@ class AnimationCurveNodeWrapper:
min_reldiff_fac = fac * 1.0e-3 # min relative value evolution: 0.1% of current 'order of magnitude'. min_reldiff_fac = fac * 1.0e-3 # min relative value evolution: 0.1% of current 'order of magnitude'.
min_absdiff_fac = 0.1 # A tenth of reldiff... min_absdiff_fac = 0.1 # A tenth of reldiff...
for values, write_mask in zip(self._frame_values_array, self._frame_write_mask_array): for values, frame_write_mask in zip(self._frame_values_array, self._frame_write_mask_array):
# Initialise to no frames written. # Initialise to no frames written.
write_mask[:] = False frame_write_mask[:] = False
# Create views of the 'previous' and 'current'
p_key_write_mask = write_mask[:-1]
key_write_mask = write_mask[1:]
p_val = values[:-1]
val = values[1:]
abs_values = np.abs(values) abs_values = np.abs(values)
p_val_abs = abs_values[:-1]
val_abs = abs_values[1:] # Create views of the 'previous' and 'current' mask and values.
p_val = values[:-1]
abs_p_val = abs_values[:-1]
p_write_mask = frame_write_mask[:-1]
val = values[1:]
abs_val = abs_values[1:]
write_mask = frame_write_mask[1:]
# This is contracted form of relative + absolute-near-zero difference: # This is contracted form of relative + absolute-near-zero difference:
# absdiff = abs(a - b) # absdiff = abs(a - b)
@ -1332,67 +1334,68 @@ class AnimationCurveNodeWrapper:
# return False # return False
# return (absdiff / ((abs(a) + abs(b)) / 2)) > min_reldiff_fac # return (absdiff / ((abs(a) + abs(b)) / 2)) > min_reldiff_fac
# Note that we ignore the '/ 2' part here, since it's not much significant for us. # Note that we ignore the '/ 2' part here, since it's not much significant for us.
enough_diff_prev_sampled_mask = ( enough_diff_p_val_mask = (
np.abs(val - p_val) > (min_reldiff_fac * np.maximum(val_abs + p_val_abs, min_absdiff_fac)) np.abs(val - p_val) > (min_reldiff_fac * np.maximum(abs_val + abs_p_val, min_absdiff_fac))
) )
# If enough difference from previous sampled value, key this value *and* the previous one! # If enough difference from previous sampled value, key this value *and* the previous one!
# Unless it is forcefully keyed later, this is the only way that the first value can be keyed. # Unless it is forcefully keyed later, this is the only way that the first value can be keyed.
p_key_write_mask[enough_diff_prev_sampled_mask] = True p_write_mask[enough_diff_p_val_mask] = True
key_write_mask[enough_diff_prev_sampled_mask] = True write_mask[enough_diff_p_val_mask] = True
# The other case where we key a value is if there is enough difference between it and the previous keyed # The other case where we key a value is if there is enough difference between it and the previous keyed
# value. # value.
# Values that equal their previous value are skipped and the remaining values to check are those which are # Values that equal their previous value can be skipped, so the remaining values to check are those which
# currently not keyed # are currently not keyed.
not_keyed_mask = ~key_write_mask not_keyed_mask = ~write_mask
check_diff_mask = np.logical_and(not_keyed_mask, p_val != val) check_diff_mask = np.logical_and(not_keyed_mask, p_val != val)
val_check_idx = np.flatnonzero(check_diff_mask) val_check_idx = np.flatnonzero(check_diff_mask)
val_check = val[val_check_idx] val_check = val[val_check_idx]
val_abs_check = val_abs[val_check_idx] abs_val_check = abs_val[val_check_idx]
# For each frame, get the index of the previous keyed value. # For each current value, get the index of the previous keyed value in `values`.
prev_keyframe_indices = np.arange(1, len(values)) p_keyed_idx = np.arange(1, len(values))
# The first 'previous keyframe' defaults to values[0], even if it's not actually keyed. # The first 'previous keyed value' defaults to values[0], even if it's not actually keyed.
prev_keyframe_indices[not_keyed_mask] = 0 p_keyed_idx[not_keyed_mask] = 0
# Accumulative maximum fills in the zeroed indices with the closest previous non-zero index. # Accumulative maximum fills in the zeroed indices with the closest previous non-zero index because the
prev_keyframe_indices = np.maximum.accumulate(prev_keyframe_indices) # indices must be increasing.
p_keyed_idx = np.maximum.accumulate(p_keyed_idx)
# Extract only the indices that need checking. # Extract only the indices that need checking.
prev_keyframe_indices = prev_keyframe_indices[val_check_idx] p_keyed_idx_check = p_keyed_idx[val_check_idx]
p_kf_vals = values[prev_keyframe_indices] p_keyed_val_check = values[p_keyed_idx_check]
p_kf_vals_abs = np.abs(p_kf_vals) abs_p_keyed_val_check = np.abs(p_keyed_val_check)
# We check our relative + absolute-near-zero difference again, but against the previous keyed value this # We check the relative + absolute-near-zero difference again, but against the previous keyed value this
# time. # time.
enough_diff_prev_keyed_mask = ( enough_diff_p_keyed_val_mask = (
np.abs(val_check - p_kf_vals) np.abs(val_check - p_keyed_val_check)
> (min_reldiff_fac * np.maximum(val_abs_check + p_kf_vals_abs, min_absdiff_fac)) > (min_reldiff_fac * np.maximum(abs_val_check + abs_p_keyed_val_check, min_absdiff_fac))
) )
if np.any(enough_diff_prev_keyed_mask): if np.any(enough_diff_p_keyed_val_mask):
# If there are any that are different enough from the previous keyed value, then we have to check them # If there are any that are different enough from the previous keyed value, then we have to check them
# all iteratively because keying a new value can change the previous keyed value of some elements, which # all iteratively because keying a new value can change the previous keyed value of some elements, which
# can change whether a value is different enough from its previous keyed value. # changes their relative + absolute-near-zero difference.
last_new_kf_idx = -1 new_p_keyed_idx = -1
last_new_kf_val = -1 new_p_keyed_val = -1
last_new_kf_val_abs = -1 new_p_keyed_val_abs = -1
# Accessing .data, the memoryview of the array iteratively or by individual index is faster than doing # Accessing .data, the memoryview of the array, iteratively or by individual index is faster than doing
# the same with the array itself. # the same with the array itself.
key_write_mv = key_write_mask.data key_write_mv = write_mask.data
zipped = zip(val_check_idx.data, val_check.data, val_abs_check.data, prev_keyframe_indices.data, zipped = zip(val_check_idx.data, val_check.data, abs_val_check.data, p_keyed_idx_check.data,
enough_diff_prev_keyed_mask.data) enough_diff_p_keyed_val_mask.data)
for cur_idx, cur_val, abs_cur_val, p_kf_idx, enough_diff in zipped: for cur_idx, cur_val, abs_cur_val, old_p_keyed_idx, enough_diff in zipped:
if last_new_kf_idx > p_kf_idx: if new_p_keyed_idx > old_p_keyed_idx:
# The previous keyframe is new and was not included when enough_diff_prev_keyed_mask was # The previous keyed value is new and was not included when enough_diff_p_keyed_val_mask was
# calculated, so whether the current value is different enough from the previous keyframe needs # calculated, so whether the current value is different enough needs to be calculated.
# to be calculated. # Check if the relative + absolute-near-zero difference is enough to key this value.
# Check if the relative + absolute-near-zero difference is enough to key this frame. enough_diff = (abs(cur_val - new_p_keyed_val)
enough_diff = (abs(cur_val - last_new_kf_val) > (min_reldiff_fac * max(abs_cur_val + new_p_keyed_val_abs, min_absdiff_fac)))
> (min_reldiff_fac * max(abs_cur_val + last_new_kf_val_abs, min_absdiff_fac)))
if enough_diff: if enough_diff:
# The current index needs to be keyed. # The current value needs to be keyed.
last_new_kf_idx = cur_idx
last_new_kf_val = cur_val
last_new_kf_val_abs = abs_cur_val
key_write_mv[cur_idx] = True key_write_mv[cur_idx] = True
# Update the index and values for this newly keyed value.
new_p_keyed_idx = cur_idx
new_p_keyed_val = cur_val
new_p_keyed_val_abs = abs_cur_val
# If we write nothing (action doing nothing) and are in 'force_keep' mode, we key everything! :P # If we write nothing (action doing nothing) and are in 'force_keep' mode, we key everything! :P
# See T41766. # See T41766.
@ -1407,10 +1410,10 @@ class AnimationCurveNodeWrapper:
# If we did key something, ensure first and last sampled values are keyed as well. # If we did key something, ensure first and last sampled values are keyed as well.
if self.force_startend_keying: if self.force_startend_keying:
for is_keyed, write_mask in zip(are_keyed, self._frame_write_mask_array): for is_keyed, frame_write_mask in zip(are_keyed, self._frame_write_mask_array):
if is_keyed: if is_keyed:
write_mask[:1] = True frame_write_mask[:1] = True
write_mask[-1:] = True frame_write_mask[-1:] = True
def get_final_data(self, scene, ref_id, force_keep=False): def get_final_data(self, scene, ref_id, force_keep=False):
""" """