|
1 | 1 | import os
|
2 | 2 | import bpy
|
| 3 | +import numpy as np |
3 | 4 | from ..utilities import report_error
|
4 | 5 | from mathutils import Vector
|
5 | 6 | from importlib.machinery import SourceFileLoader
|
@@ -38,6 +39,7 @@ def export(**keywords):
|
38 | 39 | FBX_DEFORMER_SKIN_VERSION,
|
39 | 40 | FBX_DEFORMER_CLUSTER_VERSION,
|
40 | 41 | BLENDER_OBJECT_TYPES_MESHLIKE,
|
| 42 | + FBX_KTIME, |
41 | 43 | units_convertor_iter,
|
42 | 44 | matrix4_to_array,
|
43 | 45 | 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
|
80 | 82 | depsgraph = scene_data.depsgraph
|
81 | 83 | force_keying = scene_data.settings.bake_anim_use_all_bones
|
82 | 84 | force_sek = scene_data.settings.bake_anim_force_startend_keying
|
| 85 | + gscale = scene_data.settings.global_scale |
83 | 86 |
|
84 | 87 | if objects is not None:
|
85 | 88 | # Add bones and duplis!
|
@@ -127,85 +130,167 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
|
127 | 130 | animdata_cameras = {}
|
128 | 131 | for cam_obj, cam_key in scene_data.data_cameras.items():
|
129 | 132 | 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 | + # |
148 | 203 | # 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. |
174 | 234 | scene.frame_set(back_currframe, subframe=0.0)
|
175 | 235 |
|
| 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 | + |
176 | 285 | animations = {}
|
177 | 286 |
|
178 | 287 | # 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: |
205 | 291 | 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", {})) |
209 | 294 | anim_data[1][fbx_group] = (group_key, group, fbx_gname)
|
210 | 295 |
|
211 | 296 | astack_key = get_blender_anim_stack_key(scene, ref_id)
|
|
0 commit comments