Skip to content
This repository was archived by the owner on Jun 14, 2024. It is now read-only.

Commit 8ece102

Browse files
committed
Update fbx.py
Ported Pull Request EpicGamesExt#706 by universalconquistador from EpicGamesExt/BlenderTools repo
1 parent 272c8b6 commit 8ece102

File tree

1 file changed

+157
-72
lines changed

1 file changed

+157
-72
lines changed

send2ue/core/io/fbx.py

+157-72
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import bpy
3+
import numpy as np
34
from ..utilities import report_error
45
from mathutils import Vector
56
from importlib.machinery import SourceFileLoader
@@ -38,6 +39,7 @@ def export(**keywords):
3839
FBX_DEFORMER_SKIN_VERSION,
3940
FBX_DEFORMER_CLUSTER_VERSION,
4041
BLENDER_OBJECT_TYPES_MESHLIKE,
42+
FBX_KTIME,
4143
units_convertor_iter,
4244
matrix4_to_array,
4345
get_fbx_uuid_from_key,
@@ -80,6 +82,7 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
8082
depsgraph = scene_data.depsgraph
8183
force_keying = scene_data.settings.bake_anim_use_all_bones
8284
force_sek = scene_data.settings.bake_anim_force_startend_keying
85+
gscale = scene_data.settings.global_scale
8386

8487
if objects is not None:
8588
# Add bones and duplis!
@@ -127,85 +130,167 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
127130
animdata_cameras = {}
128131
for cam_obj, cam_key in scene_data.data_cameras.items():
129132
cam = cam_obj.bdata.data
130-
acnode = AnimationCurveNodeWrapper(cam_key, 'CAMERA_FOCAL', force_key, force_sek, (cam.lens,))
131-
animdata_cameras[cam_key] = (acnode, cam)
132-
133-
currframe = f_start
134-
while currframe <= f_end:
135-
real_currframe = currframe - f_start if start_zero else currframe
136-
scene.frame_set(int(currframe), subframe=currframe - int(currframe))
137-
138-
for dp_obj in ob_obj.dupli_list_gen(depsgraph):
139-
pass # Merely updating dupli matrix of ObjectWrapper...
140-
141-
for ob_obj, (anim_loc, anim_rot, anim_scale) in animdata_ob.items():
142-
location_multiple = 100
143-
scale_factor = 1
144-
145-
# if this curve is the object root then keep its scale at 1
146-
if len(str(ob_obj).split('|')) == 1:
147-
location_multiple = 1
133+
acnode_lens = AnimationCurveNodeWrapper(cam_key, 'CAMERA_FOCAL', force_key, force_sek, (cam.lens,))
134+
acnode_focus_distance = AnimationCurveNodeWrapper(cam_key, 'CAMERA_FOCUS_DISTANCE', force_key,
135+
force_sek, (cam.dof.focus_distance,))
136+
animdata_cameras[cam_key] = (acnode_lens, acnode_focus_distance, cam)
137+
138+
# Get all parent bdata of animated dupli instances, so that we can quickly identify which instances in
139+
# `depsgraph.object_instances` are animated and need their ObjectWrappers' matrices updated each frame.
140+
dupli_parent_bdata = {dup.get_parent().bdata for dup in animdata_ob if dup.is_dupli}
141+
has_animated_duplis = bool(dupli_parent_bdata)
142+
143+
# Initialize keyframe times array. Each AnimationCurveNodeWrapper will share the same instance.
144+
# `np.arange` excludes the `stop` argument like when using `range`, so we use np.nextafter to get the next
145+
# representable value after f_end and use that as the `stop` argument instead.
146+
currframes = np.arange(f_start, np.nextafter(f_end, np.inf), step=bake_step)
147+
148+
# Convert from Blender time to FBX time.
149+
fps = scene.render.fps / scene.render.fps_base
150+
real_currframes = currframes - f_start if start_zero else currframes
151+
real_currframes = (real_currframes / fps * FBX_KTIME).astype(np.int64)
152+
153+
# Generator that yields the animated values of each frame in order.
154+
def frame_values_gen():
155+
# Precalculate integer frames and subframes.
156+
int_currframes = currframes.astype(int)
157+
subframes = currframes - int_currframes
158+
159+
# Create simpler iterables that return only the values we care about.
160+
animdata_shapes_only = [shape for _anim_shape, _me, shape in animdata_shapes.values()]
161+
animdata_cameras_only = [camera for _anim_camera_lens, _anim_camera_focus_distance, camera
162+
in animdata_cameras.values()]
163+
# Previous frame's rotation for each object in animdata_ob, this will be updated each frame.
164+
animdata_ob_p_rots = p_rots.values()
165+
166+
# Iterate through each frame and yield the values for that frame.
167+
# Iterating .data, the memoryview of an array, is faster than iterating the array directly.
168+
for int_currframe, subframe in zip(int_currframes.data, subframes.data):
169+
scene.frame_set(int_currframe, subframe=subframe)
170+
171+
if has_animated_duplis:
172+
# Changing the scene's frame invalidates existing dupli instances. To get the updated matrices of duplis
173+
# for this frame, we must get the duplis from the depsgraph again.
174+
for dup in depsgraph.object_instances:
175+
if (parent := dup.parent) and parent.original in dupli_parent_bdata:
176+
# ObjectWrapper caches its instances. Attempting to create a new instance updates the existing
177+
# ObjectWrapper instance with the current frame's matrix and then returns the existing instance.
178+
ObjectWrapper(dup)
179+
next_p_rots = []
180+
for ob_obj, p_rot in zip(animdata_ob, animdata_ob_p_rots):
181+
182+
183+
#
184+
# send2ue: Scale shennanigans
185+
#
186+
location_multiple = 100
187+
scale_factor = 1
188+
# if this curve is the object root then keep its scale at 1
189+
if len(str(ob_obj).split('|')) == 1:
190+
location_multiple = 1
191+
# Todo add to FBX addon
192+
scale_factor = SCALE_FACTOR
193+
194+
195+
196+
# We compute baked loc/rot/scale for all objects (rot being euler-compat with previous value!).
197+
loc, rot, scale, _m, _mr = ob_obj.fbx_object_tx(scene_data, rot_euler_compat=p_rot)
198+
199+
200+
#
201+
# send2ue: Make location keyframes relative to the armature object
202+
#
148203
# Todo add to FBX addon
149-
scale_factor = SCALE_FACTOR
150-
151-
# We compute baked loc/rot/scale for all objects (rot being euler-compat with previous value!).
152-
p_rot = p_rots.get(ob_obj, None)
153-
loc, rot, scale, _m, _mr = ob_obj.fbx_object_tx(scene_data, rot_euler_compat=p_rot)
154-
155-
# Todo add to FBX addon
156-
# the armature object's position is the reference we use to offset all location keyframes
157-
if ob_obj.type == 'ARMATURE':
158-
location_offset = loc
159-
# subtract the location offset from each location keyframe if the use_object_origin is on
160-
if bpy.context.scene.send2ue.use_object_origin:
161-
loc = Vector(
162-
(loc[0] - location_offset[0], loc[1] - location_offset[1], loc[2] - location_offset[2]))
163-
164-
p_rots[ob_obj] = rot
165-
anim_loc.add_keyframe(real_currframe, loc * location_multiple)
166-
anim_rot.add_keyframe(real_currframe, tuple(convert_rad_to_deg_iter(rot)))
167-
anim_scale.add_keyframe(real_currframe, scale / scale_factor)
168-
for anim_shape, me, shape in animdata_shapes.values():
169-
anim_shape.add_keyframe(real_currframe, (shape.value * scale_factor,))
170-
for anim_camera, camera in animdata_cameras.values():
171-
anim_camera.add_keyframe(real_currframe, (camera.lens,))
172-
currframe += bake_step
173-
204+
# the armature object's position is the reference we use to offset all location keyframes
205+
if ob_obj.type == 'ARMATURE':
206+
location_offset = loc
207+
# subtract the location offset from each location keyframe if the use_object_origin is on
208+
if bpy.context.scene.send2ue.use_object_origin:
209+
loc = Vector(
210+
(loc[0] - location_offset[0], loc[1] - location_offset[1], loc[2] - location_offset[2]))
211+
212+
213+
214+
next_p_rots.append(rot)
215+
yield from loc * location_multiple # send2ue: Apply translation scalar
216+
yield from rot
217+
yield from scale / scale_factor # send2ue: Apply scale factor
218+
animdata_ob_p_rots = next_p_rots
219+
for shape in animdata_shapes_only:
220+
yield shape.value
221+
for camera in animdata_cameras_only:
222+
yield camera.lens
223+
yield camera.dof.focus_distance
224+
225+
# Providing `count` to np.fromiter pre-allocates the array, avoiding extra memory allocations while iterating.
226+
num_ob_values = len(animdata_ob) * 9 # Location, rotation and scale, each of which have x, y, and z components
227+
num_shape_values = len(animdata_shapes) # Only 1 value per shape key
228+
num_camera_values = len(animdata_cameras) * 2 # Focal length (`.lens`) and focus distance
229+
num_values_per_frame = num_ob_values + num_shape_values + num_camera_values
230+
num_frames = len(real_currframes)
231+
all_values_flat = np.fromiter(frame_values_gen(), dtype=float, count=num_frames * num_values_per_frame)
232+
233+
# Restore the scene's current frame.
174234
scene.frame_set(back_currframe, subframe=0.0)
175235

236+
# View such that each column is all values for a single frame and each row is all values for a single curve.
237+
all_values = all_values_flat.reshape(num_frames, num_values_per_frame).T
238+
# Split into views of the arrays for each curve type.
239+
split_at = [num_ob_values, num_shape_values, num_camera_values]
240+
# For unequal sized splits, np.split takes indices to split at, which can be acquired through a cumulative sum
241+
# across the list.
242+
# The last value isn't needed, because the last split is assumed to go to the end of the array.
243+
split_at = np.cumsum(split_at[:-1])
244+
all_ob_values, all_shape_key_values, all_camera_values = np.split(all_values, split_at)
245+
246+
all_anims = []
247+
248+
# Set location/rotation/scale curves.
249+
# Split into equal sized views of the arrays for each object.
250+
split_into = len(animdata_ob)
251+
per_ob_values = np.split(all_ob_values, split_into) if split_into > 0 else ()
252+
for anims, ob_values in zip(animdata_ob.values(), per_ob_values):
253+
# Split again into equal sized views of the location, rotation and scaling arrays.
254+
loc_xyz, rot_xyz, sca_xyz = np.split(ob_values, 3)
255+
# In-place convert from Blender rotation to FBX rotation.
256+
np.rad2deg(rot_xyz, out=rot_xyz)
257+
258+
anim_loc, anim_rot, anim_scale = anims
259+
anim_loc.set_keyframes(real_currframes, loc_xyz)
260+
anim_rot.set_keyframes(real_currframes, rot_xyz)
261+
anim_scale.set_keyframes(real_currframes, sca_xyz)
262+
all_anims.extend(anims)
263+
264+
# Set shape key curves.
265+
# There's only one array per shape key, so there's no need to split `all_shape_key_values`.
266+
for (anim_shape, _me, _shape), shape_key_values in zip(animdata_shapes.values(), all_shape_key_values):
267+
# In-place convert from Blender Shape Key Value to FBX Deform Percent.
268+
shape_key_values *= 100.0
269+
anim_shape.set_keyframes(real_currframes, shape_key_values)
270+
all_anims.append(anim_shape)
271+
272+
# Set camera curves.
273+
# Split into equal sized views of the arrays for each camera.
274+
split_into = len(animdata_cameras)
275+
per_camera_values = np.split(all_camera_values, split_into) if split_into > 0 else ()
276+
zipped = zip(animdata_cameras.values(), per_camera_values)
277+
for (anim_camera_lens, anim_camera_focus_distance, _camera), (lens_values, focus_distance_values) in zipped:
278+
# In-place convert from Blender focus distance to FBX.
279+
focus_distance_values *= (1000 * gscale)
280+
anim_camera_lens.set_keyframes(real_currframes, lens_values)
281+
anim_camera_focus_distance.set_keyframes(real_currframes, focus_distance_values)
282+
all_anims.append(anim_camera_lens)
283+
all_anims.append(anim_camera_focus_distance)
284+
176285
animations = {}
177286

178287
# And now, produce final data (usable by FBX export code)
179-
# Objects-like loc/rot/scale...
180-
for ob_obj, anims in animdata_ob.items():
181-
for anim in anims:
182-
anim.simplify(simplify_fac, bake_step, force_keep)
183-
if not anim:
184-
continue
185-
for obj_key, group_key, group, fbx_group, fbx_gname in anim.get_final_data(scene, ref_id, force_keep):
186-
anim_data = animations.setdefault(obj_key, ("dummy_unused_key", {}))
187-
anim_data[1][fbx_group] = (group_key, group, fbx_gname)
188-
189-
# And meshes' shape keys.
190-
for channel_key, (anim_shape, me, shape) in animdata_shapes.items():
191-
final_keys = {}
192-
anim_shape.simplify(simplify_fac, bake_step, force_keep)
193-
if not anim_shape:
194-
continue
195-
for elem_key, group_key, group, fbx_group, fbx_gname in anim_shape.get_final_data(scene, ref_id,
196-
force_keep):
197-
anim_data = animations.setdefault(elem_key, ("dummy_unused_key", {}))
198-
anim_data[1][fbx_group] = (group_key, group, fbx_gname)
199-
200-
# And cameras' lens keys.
201-
for cam_key, (anim_camera, camera) in animdata_cameras.items():
202-
final_keys = {}
203-
anim_camera.simplify(simplify_fac, bake_step, force_keep)
204-
if not anim_camera:
288+
for anim in all_anims:
289+
anim.simplify(simplify_fac, bake_step, force_keep)
290+
if not anim:
205291
continue
206-
for elem_key, group_key, group, fbx_group, fbx_gname in anim_camera.get_final_data(scene, ref_id,
207-
force_keep):
208-
anim_data = animations.setdefault(elem_key, ("dummy_unused_key", {}))
292+
for obj_key, group_key, group, fbx_group, fbx_gname in anim.get_final_data(scene, ref_id, force_keep):
293+
anim_data = animations.setdefault(obj_key, ("dummy_unused_key", {}))
209294
anim_data[1][fbx_group] = (group_key, group, fbx_gname)
210295

211296
astack_key = get_blender_anim_stack_key(scene, ref_id)

0 commit comments

Comments
 (0)