FBX IO: Speed up animation export using NumPy #104884
@ -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)
|
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
|
||||||
|
|
||||||
# Get all animated values
|
# Generator that yields the animated values of each frame in order.
|
||||||
def frame_values_gen():
|
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)
|
int_currframes = currframes.astype(int)
|
||||||
subframes = currframes - int_currframes
|
subframes = currframes - int_currframes
|
||||||
|
|
||||||
# Create simpler iterables that return only the values we care about.
|
# 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_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
|
animdata_cameras_only = [camera for _anim_camera_lens, _anim_camera_focus_distance, camera
|
||||||
in animdata_cameras.values()]
|
in animdata_cameras.values()]
|
||||||
# Previous frame's rotation for each object in animdata_ob, this will be updated each frame.
|
# Previous frame's rotation for each object in animdata_ob, this will be updated each frame.
|
||||||
animdata_ob_p_rots = p_rots.values()
|
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.
|
# 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):
|
for real_currframe, int_currframe, subframe in zip(real_currframes.data, int_currframes.data, subframes.data):
|
||||||
scene.frame_set(int_currframe, subframe=subframe)
|
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.lens
|
||||||
yield camera.dof.focus_distance
|
yield camera.dof.focus_distance
|
||||||
|
|
||||||
# Calculating the total expected number of values reduces memory allocations while iterating and ensures the array
|
# Providing `count` to np.fromiter pre-allocates the array, avoiding extra memory allocations while iterating.
|
||||||
# ends up the size we're expecting.
|
num_ob_values = len(animdata_ob) * 9 # Location, rotation and scale, each of which have x, y, and z components
|
||||||
num_ob_loc_values = num_ob_rot_values = num_ob_scale_values = 3
|
num_shape_values = len(animdata_shapes) # Only 1 value per shape key
|
||||||
num_values_per_ob = num_ob_loc_values + num_ob_rot_values + num_ob_scale_values
|
num_camera_values = len(animdata_cameras) * 2 # Focal length (`.lens`) and focus distance
|
||||||
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
|
|
||||||
num_values_per_frame = num_ob_values + num_shape_values + num_camera_values
|
num_values_per_frame = num_ob_values + num_shape_values + num_camera_values
|
||||||
num_frames = len(real_currframes)
|
num_frames = len(real_currframes)
|
||||||
total_num_values = num_frames * num_values_per_frame
|
all_values_flat = np.fromiter(frame_values_gen(), dtype=float, count=num_frames * num_values_per_frame)
|
||||||
all_values = np.fromiter(frame_values_gen(), dtype=np.float64, count=total_num_values)
|
|
||||||
|
# Restore the scene's current frame.
|
||||||
scene.frame_set(back_currframe, subframe=0.0)
|
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
|
# View such that each column is all values for a single frame and each row is all values for a single curve.
|
||||||
# curve.
|
all_values = all_values_flat.reshape(num_frames, num_values_per_frame).T
|
||||||
all_values = all_values.reshape(num_frames, -1).T
|
|
||||||
# Split into views of the arrays for each curve type.
|
# Split into views of the arrays for each curve type.
|
||||||
split_at = [num_ob_values, num_shape_values, num_camera_values]
|
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.
|
# 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]
|
split_at = np.cumsum(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)
|
|
||||||
all_ob_values, all_shape_key_values, all_camera_values = np.split(all_values, split_at)
|
all_ob_values, all_shape_key_values, all_camera_values = np.split(all_values, split_at)
|
||||||
|
|
||||||
# Set location/rotation/scale curves
|
# Set location/rotation/scale curves.
|
||||||
# Further split into views of the arrays for each object.
|
# Split into equal sized views of the arrays for each object.
|
||||||
num_animdata_ob = len(animdata_ob)
|
split_into = len(animdata_ob)
|
||||||
all_ob_values = np.split(all_ob_values, num_animdata_ob) if num_animdata_ob else ()
|
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(), all_ob_values):
|
for (anim_loc, anim_rot, anim_scale), ob_values in zip(animdata_ob.values(), per_ob_values):
|
||||||
# Further split into views of the location, rotation and scaling arrays.
|
# Split again into equal sized views of the location, rotation and scaling arrays.
|
||||||
loc_xyz, rot_xyz, sca_xyz = np.split(ob_values, 3)
|
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)
|
np.rad2deg(rot_xyz, out=rot_xyz)
|
||||||
|
|
||||||
anim_loc.set_keyframes(real_currframes, loc_xyz)
|
anim_loc.set_keyframes(real_currframes, loc_xyz)
|
||||||
anim_rot.set_keyframes(real_currframes, rot_xyz)
|
anim_rot.set_keyframes(real_currframes, rot_xyz)
|
||||||
anim_scale.set_keyframes(real_currframes, sca_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):
|
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.
|
# In-place convert from Blender Shape Key Value to FBX Deform Percent.
|
||||||
shape_key_values *= 100.0
|
shape_key_values *= 100.0
|
||||||
anim_shape.set_keyframes(real_currframes, shape_key_values)
|
anim_shape.set_keyframes(real_currframes, shape_key_values)
|
||||||
|
|
||||||
# Set camera curves
|
# Set camera curves.
|
||||||
# Further split into views of the arrays for each camera.
|
# Split into equal sized views of the arrays for each camera.
|
||||||
num_animdata_cameras = len(animdata_cameras)
|
split_into = len(animdata_cameras)
|
||||||
all_camera_values = np.split(all_camera_values, num_animdata_cameras) if num_animdata_cameras else ()
|
per_camera_values = np.split(all_camera_values, split_into) if split_into > 0 else ()
|
||||||
for (anim_camera_lens, anim_camera_focus_distance, camera), camera_values in zip(animdata_cameras.values(), all_camera_values):
|
zipped = zip(animdata_cameras.values(), per_camera_values)
|
||||||
lens_values, focus_distance_values = camera_values
|
for (anim_camera_lens, anim_camera_focus_distance, _camera), (lens_values, focus_distance_values) in zipped:
|
||||||
# In-place convert from Blender to FBX
|
# In-place convert from Blender focus distance to FBX.
|
||||||
focus_distance_values *= (1000 * gscale)
|
focus_distance_values *= (1000 * gscale)
|
||||||
anim_camera_lens.set_keyframes(real_currframes, lens_values)
|
anim_camera_lens.set_keyframes(real_currframes, lens_values)
|
||||||
anim_camera_focus_distance.set_keyframes(real_currframes, focus_distance_values)
|
anim_camera_focus_distance.set_keyframes(real_currframes, focus_distance_values)
|
||||||
|
Loading…
Reference in New Issue
Block a user