FBX IO: Speed up animation simplification using NumPy #104904
@ -2260,17 +2260,20 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
|
||||
currframes = np.arange(f_start, np.nextafter(f_end, np.inf), step=bake_step)
|
||||
real_currframes = currframes - f_start if start_zero else currframes
|
||||
|
||||
# Get all animated values
|
||||
# Generator that yields the animated values of each frame in order.
|
||||
def frame_values_gen():
|
||||
# Iterate through each frame and yield the values for that frame.
|
||||
# Precalculate integer frames and subframes.
|
||||
int_currframes = currframes.astype(int)
|
||||
subframes = currframes - int_currframes
|
||||
|
||||
# Create simpler iterables that return only the values we care about.
|
||||
animdata_shapes_only = [shape for _anim_shape, _me, shape in animdata_shapes.values()]
|
||||
animdata_cameras_only = [camera for _anim_camera_lens, _anim_camera_focus_distance, camera
|
||||
in animdata_cameras.values()]
|
||||
# Previous frame's rotation for each object in animdata_ob, this will be updated each frame.
|
||||
animdata_ob_p_rots = p_rots.values()
|
||||
|
||||
# Iterate through each frame and yield the values for that frame.
|
||||
# Iterating .data, the memoryview of an array, is faster than iterating the array directly.
|
||||
for real_currframe, int_currframe, subframe in zip(real_currframes.data, int_currframes.data, subframes.data):
|
||||
scene.frame_set(int_currframe, subframe=subframe)
|
||||
@ -2298,59 +2301,55 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
|
||||
yield camera.lens
|
||||
yield camera.dof.focus_distance
|
||||
|
||||
# Calculating the total expected number of values reduces memory allocations while iterating and ensures the array
|
||||
# ends up the size we're expecting.
|
||||
num_ob_loc_values = num_ob_rot_values = num_ob_scale_values = 3
|
||||
num_values_per_ob = num_ob_loc_values + num_ob_rot_values + num_ob_scale_values
|
||||
num_ob_values = len(animdata_ob) * num_values_per_ob
|
||||
num_shape_values = len(animdata_shapes)
|
||||
num_values_per_camera = 2
|
||||
num_camera_values = len(animdata_cameras) * num_values_per_camera
|
||||
# Providing `count` to np.fromiter pre-allocates the array, avoiding extra memory allocations while iterating.
|
||||
num_ob_values = len(animdata_ob) * 9 # Location, rotation and scale, each of which have x, y, and z components
|
||||
num_shape_values = len(animdata_shapes) # Only 1 value per shape key
|
||||
num_camera_values = len(animdata_cameras) * 2 # Focal length (`.lens`) and focus distance
|
||||
num_values_per_frame = num_ob_values + num_shape_values + num_camera_values
|
||||
num_frames = len(real_currframes)
|
||||
total_num_values = num_frames * num_values_per_frame
|
||||
all_values = np.fromiter(frame_values_gen(), dtype=np.float64, count=total_num_values)
|
||||
all_values_flat = np.fromiter(frame_values_gen(), dtype=float, count=num_frames * num_values_per_frame)
|
||||
|
||||
# Restore the scene's current frame.
|
||||
scene.frame_set(back_currframe, subframe=0.0)
|
||||
|
||||
# View as each column being the values for a single frame and each row being all values for a single property in a
|
||||
# curve.
|
||||
all_values = all_values.reshape(num_frames, -1).T
|
||||
# View such that each column is all values for a single frame and each row is all values for a single curve.
|
||||
all_values = all_values_flat.reshape(num_frames, num_values_per_frame).T
|
||||
# Split into views of the arrays for each curve type.
|
||||
split_at = [num_ob_values, num_shape_values, num_camera_values]
|
||||
# For unequal sized splits, np.split takes indices to split at, which can be acquired through a cumulative sum
|
||||
# across the list.
|
||||
# The last value isn't needed, because the last split is assumed to go to the end of the array.
|
||||
split_at = split_at[:-1]
|
||||
# For uneven splits, np.split takes indices to split at, which can be acquired through a cumulative sum across the
|
||||
# list.
|
||||
split_at = np.cumsum(split_at)
|
||||
split_at = np.cumsum(split_at[:-1])
|
||||
all_ob_values, all_shape_key_values, all_camera_values = np.split(all_values, split_at)
|
||||
|
||||
# Set location/rotation/scale curves
|
||||
# Further split into views of the arrays for each object.
|
||||
num_animdata_ob = len(animdata_ob)
|
||||
all_ob_values = np.split(all_ob_values, num_animdata_ob) if num_animdata_ob else ()
|
||||
for (anim_loc, anim_rot, anim_scale), ob_values in zip(animdata_ob.values(), all_ob_values):
|
||||
# Further split into views of the location, rotation and scaling arrays.
|
||||
# Set location/rotation/scale curves.
|
||||
# Split into equal sized views of the arrays for each object.
|
||||
split_into = len(animdata_ob)
|
||||
per_ob_values = np.split(all_ob_values, split_into) if split_into > 0 else ()
|
||||
for (anim_loc, anim_rot, anim_scale), ob_values in zip(animdata_ob.values(), per_ob_values):
|
||||
# Split again into equal sized views of the location, rotation and scaling arrays.
|
||||
loc_xyz, rot_xyz, sca_xyz = np.split(ob_values, 3)
|
||||
# In-place convert to degrees.
|
||||
# In-place convert from Blender rotation to FBX rotation.
|
||||
np.rad2deg(rot_xyz, out=rot_xyz)
|
||||
|
||||
anim_loc.set_keyframes(real_currframes, loc_xyz)
|
||||
anim_rot.set_keyframes(real_currframes, rot_xyz)
|
||||
anim_scale.set_keyframes(real_currframes, sca_xyz)
|
||||
|
||||
# Set shape key curves
|
||||
# Set shape key curves.
|
||||
# There's only one array per shape key, so there's no need to split `all_shape_key_values`.
|
||||
for (anim_shape, _me, _shape), shape_key_values in zip(animdata_shapes.values(), all_shape_key_values):
|
||||
# In-place convert from Blender Shape Key Value to FBX Deform Percent.
|
||||
shape_key_values *= 100.0
|
||||
anim_shape.set_keyframes(real_currframes, shape_key_values)
|
||||
|
||||
# Set camera curves
|
||||
# Further split into views of the arrays for each camera.
|
||||
num_animdata_cameras = len(animdata_cameras)
|
||||
all_camera_values = np.split(all_camera_values, num_animdata_cameras) if num_animdata_cameras else ()
|
||||
for (anim_camera_lens, anim_camera_focus_distance, camera), camera_values in zip(animdata_cameras.values(), all_camera_values):
|
||||
lens_values, focus_distance_values = camera_values
|
||||
# In-place convert from Blender to FBX
|
||||
# Set camera curves.
|
||||
# Split into equal sized views of the arrays for each camera.
|
||||
split_into = len(animdata_cameras)
|
||||
per_camera_values = np.split(all_camera_values, split_into) if split_into > 0 else ()
|
||||
zipped = zip(animdata_cameras.values(), per_camera_values)
|
||||
for (anim_camera_lens, anim_camera_focus_distance, _camera), (lens_values, focus_distance_values) in zipped:
|
||||
# In-place convert from Blender focus distance to FBX.
|
||||
focus_distance_values *= (1000 * gscale)
|
||||
anim_camera_lens.set_keyframes(real_currframes, lens_values)
|
||||
anim_camera_focus_distance.set_keyframes(real_currframes, focus_distance_values)
|
||||
|
Loading…
Reference in New Issue
Block a user