diff options
author | Julien Duroure <julien.duroure@gmail.com> | 2019-05-26 23:34:29 +0300 |
---|---|---|
committer | Julien Duroure <julien.duroure@gmail.com> | 2019-05-26 23:34:29 +0300 |
commit | 295984b200890cf6d5bdea6af5a3bd3d4260b765 (patch) | |
tree | 5f98e7b7c5630e847cb9d670387b243bf72b30d0 | |
parent | e66bf5191ff2ae470b0551c16436de6f0923c07f (diff) |
glTF exporter: fix animation when a property is single keyframed (not all property index)
-rwxr-xr-x | io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py | 57 | ||||
-rwxr-xr-x | io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py | 70 |
2 files changed, 113 insertions, 14 deletions
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py index 34e4c5e0..a6cd0f9e 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_sampler_keyframes.py @@ -37,7 +37,7 @@ class Keyframe: self.__in_tangent = None self.__out_tangent = None - def __get_target_len(self): + def get_target_len(self): length = { "delta_location": 3, "delta_rotation_euler": 3, @@ -61,12 +61,23 @@ class Keyframe: # Sometimes blender animations only reference a subset of components of a data target. Keyframe should always # contain a complete Vector/ Quaternion --> use the array_index value of the keyframe to set components in such # structures - result = [0.0] * self.__get_target_len() + result = [0.0] * self.get_target_len() for i, v in zip(self.__indices, value): result[i] = v result = gltf2_blender_math.list_to_mathutils(result, self.target) return result + def get_indices(self): + return self.__indices + + def set_value_index(self, idx, val): + self.__value[idx] = val + + def set_full_value(self, val): + self.__value = [0.0] * self.get_target_len() + for i in range(0, self.get_target_len()): + self.set_value_index(i, val[i]) + @property def value(self) -> typing.Union[mathutils.Vector, mathutils.Euler, mathutils.Quaternion, typing.List[float]]: return self.__value @@ -96,7 +107,9 @@ class Keyframe: @cached def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Object], channels: typing.Tuple[bpy.types.FCurve], - export_settings) -> typing.List[Keyframe]: + non_keyed_values: typing.Tuple[typing.Optional[float]], + export_settings + ) -> typing.List[Keyframe]: """Convert the blender action groups' fcurves to keyframes for use in glTF.""" # Find the start and end of the whole action group ranges = [channel.range() for channel in channels] @@ -105,16 +118,17 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec end_frame = max([channel.range()[1] for channel in channels]) keyframes = [] + + if blender_object_if_armature is not None: + pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature, + channels[0].data_path) + else: + pose_bone_if_armature = None + if needs_baking(blender_object_if_armature, channels, export_settings): # Bake the animation, by evaluating the animation for all frames # TODO: maybe baking can also be done with FCurve.convert_to_samples - if blender_object_if_armature is not None: - pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature, - channels[0].data_path) - else: - pose_bone_if_armature = None - # sample all frames frame = start_frame step = export_settings['gltf_frame_step'] @@ -123,18 +137,21 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec if isinstance(pose_bone_if_armature, bpy.types.PoseBone): # we need to bake in the constraints bpy.context.scene.frame_set(frame) + # TODO, this is not working if the action is not active (NLA case for example) trans, rot, scale = pose_bone_if_armature.matrix_basis.decompose() target_property = channels[0].data_path.split('.')[-1] - key.value = { + # Store all values, not only the data from fcurve: + # All indices must be stored + key.set_full_value({ "location": trans, "rotation_axis_angle": rot, "rotation_euler": rot, "rotation_quaternion": rot, "scale": scale - }[target_property] - + }[target_property]) else: key.value = [c.evaluate(frame) for c in channels] + complete_key(key, non_keyed_values) keyframes.append(key) frame += step else: @@ -142,8 +159,10 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec frames = [keyframe.co[0] for keyframe in channels[0].keyframe_points] for i, frame in enumerate(frames): key = Keyframe(channels, frame) - # key.value = [c.keyframe_points[i].co[0] for c in action_group.channels] key.value = [c.evaluate(frame) for c in channels] + # Complete key with non keyed values, if needed + if len(channels) != key.get_target_len(): + complete_key(key, non_keyed_values) # compute tangents for cubic spline interpolation if channels[0].keyframe_points[0].interpolation == "BEZIER": @@ -179,6 +198,18 @@ def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Objec return keyframes +def complete_key(key: Keyframe, non_keyed_values: typing.Tuple[typing.Optional[float]]): + """ + Complete keyframe with non keyed values + """ + + if key.target == "value": + return # No array_index + for i in range(0, key.get_target_len()): + if i in key.get_indices(): + continue # this is a keyed array_index + key.set_value_index(i, non_keyed_values[i]) + def needs_baking(blender_object_if_armature: typing.Optional[bpy.types.Object], channels: typing.Tuple[bpy.types.FCurve], export_settings diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py index 827ec91e..cdd4be7b 100755 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_animation_samplers.py @@ -34,17 +34,81 @@ def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve], blender_object: bpy.types.Object, export_settings ) -> gltf2_io.AnimationSampler: + blender_object_if_armature = blender_object if blender_object.type == "ARMATURE" else None + if blender_object_if_armature is not None: + pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature, + channels[0].data_path) + else: + pose_bone_if_armature = None + non_keyed_values = __gather_non_keyed_values(channels, blender_object, + blender_object_if_armature, pose_bone_if_armature, + export_settings) + + return gltf2_io.AnimationSampler( extensions=__gather_extensions(channels, blender_object_if_armature, export_settings), extras=__gather_extras(channels, blender_object_if_armature, export_settings), - input=__gather_input(channels, blender_object_if_armature, export_settings), + input=__gather_input(channels, blender_object_if_armature, non_keyed_values, export_settings), interpolation=__gather_interpolation(channels, blender_object_if_armature, export_settings), output=__gather_output(channels, blender_object.matrix_parent_inverse.copy().freeze(), blender_object_if_armature, + non_keyed_values, export_settings) ) +def __gather_non_keyed_values(channels: typing.Tuple[bpy.types.FCurve], + blender_object: bpy.types.Object, + blender_object_if_armature: typing.Optional[bpy.types.Object], + pose_bone_if_armature: typing.Optional[bpy.types.PoseBone], + export_settings + ) -> typing.Tuple[typing.Optional[float]]: + + non_keyed_values = [] + + target = channels[0].data_path.split('.')[-1] + if target == "value": + return () + + indices = [c.array_index for c in channels] + indices.sort() + length = { + "delta_location": 3, + "delta_rotation_euler": 3, + "location": 3, + "rotation_axis_angle": 4, + "rotation_euler": 3, + "rotation_quaternion": 4, + "scale": 3, + "value": 1 + }.get(target) + + for i in range(0, length): + if i in indices: + non_keyed_values.append(None) + else: + if blender_object_if_armature is None: + non_keyed_values.append({ + "delta_location" : blender_object.delta_location, + "delta_rotation_euler" : blender_object.delta_rotation_euler, + "location" : blender_object.location, + "rotation_axis_angle" : blender_object.rotation_axis_angle, + "rotation_euler" : blender_object.rotation_euler, + "rotation_quaternion" : blender_object.rotation_quaternion, + "scale" : blender_object.scale + }[target][i]) + else: + # TODO, this is not working if the action is not active (NLA case for example) + trans, rot, scale = pose_bone_if_armature.matrix_basis.decompose() + non_keyed_values.append({ + "location": trans, + "rotation_axis_angle": rot, + "rotation_euler": rot, + "rotation_quaternion": rot, + "scale": scale + }[target][i]) + + return tuple(non_keyed_values) def __gather_extensions(channels: typing.Tuple[bpy.types.FCurve], blender_object_if_armature: typing.Optional[bpy.types.Object], @@ -63,11 +127,13 @@ def __gather_extras(channels: typing.Tuple[bpy.types.FCurve], @cached def __gather_input(channels: typing.Tuple[bpy.types.FCurve], blender_object_if_armature: typing.Optional[bpy.types.Object], + non_keyed_values: typing.Tuple[typing.Optional[float]], export_settings ) -> gltf2_io.Accessor: """Gather the key time codes.""" keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_object_if_armature, channels, + non_keyed_values, export_settings) times = [k.seconds for k in keyframes] @@ -107,11 +173,13 @@ def __gather_interpolation(channels: typing.Tuple[bpy.types.FCurve], def __gather_output(channels: typing.Tuple[bpy.types.FCurve], parent_inverse, blender_object_if_armature: typing.Optional[bpy.types.Object], + non_keyed_values: typing.Tuple[typing.Optional[float]], export_settings ) -> gltf2_io.Accessor: """Gather the data of the keyframes.""" keyframes = gltf2_blender_gather_animation_sampler_keyframes.gather_keyframes(blender_object_if_armature, channels, + non_keyed_values, export_settings) target_datapath = channels[0].data_path |