FBX IO: Speed up animation export using NumPy #104884
@ -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
|
||||||
|
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user