def wrapper_bonecache(*args, **kwargs):

        armature = args[-1]['vtree'].nodes[args[0]].blender_object

        cache_key_args = args
        cache_key_args = args[:-1]

        if cache_key_args[2] is None:
            pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(
                armature, cache_key_args[1][0].data_path)
        else:
            pose_bone_if_armature = armature.pose.bones[cache_key_args[2]]

        if not hasattr(func, "__current_action_name"):
            func.reset_cache()
        if cache_key_args[6] != func.__current_action_name or cache_key_args[
                0] != func.__current_armature_uuid:
            result = func(*args)
            func.__bonecache = result
            func.__current_action_name = cache_key_args[6]
            func.__current_armature_uuid = cache_key_args[0]
            return result[cache_key_args[7]][pose_bone_if_armature.name]
        else:
            return func.__bonecache[cache_key_args[7]][
                pose_bone_if_armature.name]
def __get_channel_groups(blender_action: bpy.types.Action, blender_object: bpy.types.Object):
    targets = {}
    for fcurve in blender_action.fcurves:
        target_property = get_target_property_name(fcurve.data_path)
        object_path = get_target_object_path(fcurve.data_path)

        # find the object affected by this action
        if not object_path:
            target = blender_object
        else:
            try:
                target = gltf2_blender_get.get_object_from_datapath(blender_object, object_path)
            except ValueError as e:
                # if the object is a mesh and the action target path can not be resolved, we know that this is a morph
                # animation.
                if blender_object.type == "MESH":
                    # if you need the specific shape key for some reason, this is it:
                    # shape_key = blender_object.data.shape_keys.path_resolve(object_path)
                    target = blender_object.data.shape_keys
                else:
                    gltf2_io_debug.print_console("WARNING", "Animation target {} not found".format(object_path))
                    continue

        # group channels by target object and affected property of the target
        target_properties = targets.get(target, {})
        channels = target_properties.get(target_property, [])
        channels.append(fcurve)
        target_properties[target_property] = channels
        targets[target] = target_properties

    groups = []
    for p in targets.values():
        groups += list(p.values())

    return map(tuple, groups)
def __gather_armature_object_channel_groups(blender_action: bpy.types.Action,
                                            blender_object: bpy.types.Object,
                                            export_settings):

    targets = {}

    if blender_object.type != "ARMATURE":
        return tuple()

    delta_rotation_detection = [False, False]  # Normal / Delta

    for fcurve in blender_action.fcurves:
        object_path = get_target_object_path(fcurve.data_path)
        if object_path != "":
            continue

        # In some invalid files, channel hasn't any keyframes ... this channel need to be ignored
        if len(fcurve.keyframe_points) == 0:
            continue
        try:
            target_property = get_target_property_name(fcurve.data_path)
        except:
            gltf2_io_debug.print_console(
                "WARNING", "Invalid animation fcurve name on action {}".format(
                    blender_action.name))
            continue
        target = gltf2_blender_get.get_object_from_datapath(
            blender_object, object_path)

        # Detect that armature is not multiple keyed for euler and quaternion
        # Keep only the current rotation mode used by bone
        rotation, delta, rotation_modes = get_rotation_modes(target_property)

        # Delta rotation management
        if delta is False:
            if delta_rotation_detection[
                    1] is True:  # normal rotation coming, but delta is already present
                continue
            delta_rotation_detection[0] = True
        else:
            if delta_rotation_detection[
                    0] is True:  # delta rotation coming, but normal is already present
                continue
            delta_rotation_detection[1] = True

        if rotation and target.rotation_mode not in rotation_modes:
            continue

        # group channels by target object and affected property of the target
        target_properties = targets.get(target, {})
        channels = target_properties.get(target_property, [])
        channels.append(fcurve)
        target_properties[target_property] = channels
        targets[target] = target_properties

    groups = []
    for p in targets.values():
        groups += list(p.values())

    return map(tuple, groups)
def needs_baking(blender_object_if_armature: typing.Optional[bpy.types.Object],
                 channels: typing.Tuple[bpy.types.FCurve],
                 export_settings
                 ) -> bool:
    """
    Check if baking is needed.

    Some blender animations need to be baked as they can not directly be expressed in glTF.
    """
    def all_equal(lst):
        return lst[1:] == lst[:-1]

    # Sampling is forced
    if export_settings[gltf2_blender_export_keys.FORCE_SAMPLING]:
        return True

    # Sampling due to unsupported interpolation
    interpolation = channels[0].keyframe_points[0].interpolation
    if interpolation not in ["BEZIER", "LINEAR", "CONSTANT"]:
        gltf2_io_debug.print_console("WARNING",
                                     "Baking animation because of an unsupported interpolation method: {}".format(
                                         interpolation)
                                     )
        return True

    if any(any(k.interpolation != interpolation for k in c.keyframe_points) for c in channels):
        # There are different interpolation methods in one action group
        gltf2_io_debug.print_console("WARNING",
                                     "Baking animation because there are keyframes with different "
                                     "interpolation methods in one channel"
                                     )
        return True

    if not all_equal([len(c.keyframe_points) for c in channels]):
        gltf2_io_debug.print_console("WARNING",
                                     "Baking animation because the number of keyframes is not "
                                     "equal for all channel tracks")
        return True

    if len(channels[0].keyframe_points) <= 1:
        # we need to bake to 'STEP', as at least two keyframes are required to interpolate
        return True

    if not all(all_equal(key_times) for key_times in zip([[k.co[0] for k in c.keyframe_points] for c in channels])):
        # The channels have differently located keyframes
        gltf2_io_debug.print_console("WARNING",
                                     "Baking animation because of differently located keyframes in one channel")
        return True

    if blender_object_if_armature is not None:
        animation_target = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature, channels[0].data_path)
        if isinstance(animation_target, bpy.types.PoseBone):
            if len(animation_target.constraints) != 0:
                # Constraints such as IK act on the bone -> can not be represented in glTF atm
                gltf2_io_debug.print_console("WARNING",
                                             "Baking animation because of unsupported constraints acting on the bone")
                return True

    return False
def needs_baking(blender_object_if_armature: typing.Optional[bpy.types.Object],
                 channels: typing.Tuple[bpy.types.FCurve],
                 export_settings
                 ) -> bool:
    """
    Check if baking is needed.

    Some blender animations need to be baked as they can not directly be expressed in glTF.
    """
    def all_equal(lst):
        return lst[1:] == lst[:-1]

    # Sampling is forced
    if export_settings[gltf2_blender_export_keys.FORCE_SAMPLING]:
        return True

    # Sampling due to unsupported interpolation
    interpolation = channels[0].keyframe_points[0].interpolation
    if interpolation not in ["BEZIER", "LINEAR", "CONSTANT"]:
        gltf2_io_debug.print_console("WARNING",
                                     "Baking animation because of an unsupported interpolation method: {}".format(
                                         interpolation)
                                     )
        return True

    if any(any(k.interpolation != interpolation for k in c.keyframe_points) for c in channels):
        # There are different interpolation methods in one action group
        gltf2_io_debug.print_console("WARNING",
                                     "Baking animation because there are keyframes with different "
                                     "interpolation methods in one channel"
                                     )
        return True

    if not all_equal([len(c.keyframe_points) for c in channels]):
        gltf2_io_debug.print_console("WARNING",
                                     "Baking animation because the number of keyframes is not "
                                     "equal for all channel tracks")
        return True

    if len(channels[0].keyframe_points) <= 1:
        # we need to bake to 'STEP', as at least two keyframes are required to interpolate
        return True

    if not all_equal(list(zip([[k.co[0] for k in c.keyframe_points] for c in channels]))):
        # The channels have differently located keyframes
        gltf2_io_debug.print_console("WARNING",
                                     "Baking animation because of differently located keyframes in one channel")
        return True

    if blender_object_if_armature is not None:
        animation_target = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature, channels[0].data_path)
        if isinstance(animation_target, bpy.types.PoseBone):
            if len(animation_target.constraints) != 0:
                # Constraints such as IK act on the bone -> can not be represented in glTF atm
                gltf2_io_debug.print_console("WARNING",
                                             "Baking animation because of unsupported constraints acting on the bone")
                return True

    return False
def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
                             blender_object: bpy.types.Object,
                             bake_bone: typing.Union[str, None],
                             bake_channel: typing.Union[str, None],
                             bake_range_start,
                             bake_range_end,
                             action_name: str,
                             driver_obj,
                             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 and driver_obj is None:
        if bake_bone is 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 = blender_object.pose.bones[bake_bone]
    else:
        pose_bone_if_armature = None
    non_keyed_values = __gather_non_keyed_values(channels, blender_object,
                                                 blender_object_if_armature, pose_bone_if_armature,
                                                 bake_channel,
                                                 driver_obj,
                                                 export_settings)


    sampler = gltf2_io.AnimationSampler(
        extensions=__gather_extensions(channels, blender_object_if_armature, export_settings, bake_bone, bake_channel),
        extras=__gather_extras(channels, blender_object_if_armature, export_settings, bake_bone, bake_channel),
        input=__gather_input(channels, blender_object_if_armature, non_keyed_values,
                             bake_bone, bake_channel, bake_range_start, bake_range_end, action_name, driver_obj, export_settings),
        interpolation=__gather_interpolation(channels, blender_object_if_armature, export_settings, bake_bone, bake_channel),
        output=__gather_output(channels, blender_object.matrix_parent_inverse.copy().freeze(),
                               blender_object_if_armature,
                               non_keyed_values,
                               bake_bone,
                               bake_channel,
                               bake_range_start,
                               bake_range_end,
                               action_name,
                               driver_obj,
                               export_settings)
    )

    export_user_extensions('gather_animation_sampler_hook',
                            export_settings,
                            sampler,
                            channels,
                            blender_object,
                            bake_bone,
                            bake_channel,
                            bake_range_start,
                            bake_range_end,
                            action_name)

    return sampler
Exemplo n.º 7
0
def __get_channel_groups(blender_action: bpy.types.Action,
                         blender_object: bpy.types.Object, export_settings):
    targets = {}
    for fcurve in blender_action.fcurves:
        # In some invalid files, channel hasn't any keyframes ... this channel need to be ignored
        if len(fcurve.keyframe_points) == 0:
            continue
        try:
            target_property = get_target_property_name(fcurve.data_path)
        except:
            gltf2_io_debug.print_console(
                "WARNING", "Invalid animation fcurve name on action {}".format(
                    blender_action.name))
            continue
        object_path = get_target_object_path(fcurve.data_path)

        # find the object affected by this action
        if not object_path:
            target = blender_object
        else:
            try:
                target = gltf2_blender_get.get_object_from_datapath(
                    blender_object, object_path)
                if blender_object.type == "MESH" and object_path.startswith(
                        "key_blocks"):
                    shape_key = blender_object.data.shape_keys.path_resolve(
                        object_path)
                    if shape_key.mute is True:
                        continue
                    target = blender_object.data.shape_keys
            except ValueError as e:
                # if the object is a mesh and the action target path can not be resolved, we know that this is a morph
                # animation.
                if blender_object.type == "MESH":
                    shape_key = blender_object.data.shape_keys.path_resolve(
                        object_path)
                    if shape_key.mute is True:
                        continue
                    target = blender_object.data.shape_keys
                else:
                    gltf2_io_debug.print_console(
                        "WARNING",
                        "Animation target {} not found".format(object_path))
                    continue

        # group channels by target object and affected property of the target
        target_properties = targets.get(target, {})
        channels = target_properties.get(target_property, [])
        channels.append(fcurve)
        target_properties[target_property] = channels
        targets[target] = target_properties

    groups = []
    for p in targets.values():
        groups += list(p.values())

    return map(tuple, groups)
Exemplo n.º 8
0
    def wrapper_bonecache(*args, **kwargs):
        if args[2] is None:
            pose_bone_if_armature = gltf2_blender_get.get_object_from_datapath(
                args[0], args[1][0].data_path)
        else:
            pose_bone_if_armature = args[0].pose.bones[args[2]]

        if not hasattr(func, "__current_action_name"):
            func.__current_action_name = None
            func.__bonecache = {}
        if args[6] != func.__current_action_name:
            result = func(*args)
            func.__bonecache = result
            func.__current_action_name = args[6]
            return result[args[7]][pose_bone_if_armature.name]
        else:
            return func.__bonecache[args[7]][pose_bone_if_armature.name]
def __get_channel_groups(blender_action: bpy.types.Action,
                         blender_object: bpy.types.Object, export_settings):
    targets = {}
    for fcurve in blender_action.fcurves:
        try:
            target_property = get_target_property_name(fcurve.data_path)
        except:
            gltf2_io_debug.print_console(
                "WARNING", "Invalid animation fcurve name on action {}".format(
                    blender_action.name))
            continue
        object_path = get_target_object_path(fcurve.data_path)

        # find the object affected by this action
        if not object_path:
            target = blender_object
        else:
            try:
                target = gltf2_blender_get.get_object_from_datapath(
                    blender_object, object_path)
            except ValueError as e:
                # if the object is a mesh and the action target path can not be resolved, we know that this is a morph
                # animation.
                if blender_object.type == "MESH":
                    # if you need the specific shape key for some reason, this is it:
                    # shape_key = blender_object.data.shape_keys.path_resolve(object_path)
                    target = blender_object.data.shape_keys
                else:
                    gltf2_io_debug.print_console(
                        "WARNING",
                        "Animation target {} not found".format(object_path))
                    continue

        # group channels by target object and affected property of the target
        target_properties = targets.get(target, {})
        channels = target_properties.get(target_property, [])
        channels.append(fcurve)
        target_properties[target_property] = channels
        targets[target] = target_properties

    groups = []
    for p in targets.values():
        groups += list(p.values())

    return map(tuple, groups)
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]],
                    bake_bone: typing.Union[str, None],
                    bake_channel: typing.Union[str, None],
                    bake_range_start,
                    bake_range_end,
                    action_name,
                    driver_obj,
                    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,
                                                                                  bake_bone,
                                                                                  bake_channel,
                                                                                  bake_range_start,
                                                                                  bake_range_end,
                                                                                  action_name,
                                                                                  driver_obj,
                                                                                  export_settings)
    if bake_bone is not None:
        target_datapath = "pose.bones['" + bake_bone + "']." + bake_channel
    else:
        target_datapath = [c for c in channels if c is not None][0].data_path

    is_yup = export_settings[gltf2_blender_export_keys.YUP]

    # bone animations need to be handled differently as they are in a different coordinate system
    if bake_bone is None:
        object_path = get_target_object_path(target_datapath)
    else:
        object_path = None
    is_armature_animation = bake_bone is not None or (blender_object_if_armature is not None and object_path != "")

    if is_armature_animation:
        if bake_bone is None:
            bone = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature, object_path)
        else:
            bone = blender_object_if_armature.pose.bones[bake_bone]
        if isinstance(bone, bpy.types.PoseBone):
            if bone.parent is None:
                axis_basis_change = mathutils.Matrix.Identity(4)
                if export_settings[gltf2_blender_export_keys.YUP]:
                    axis_basis_change = mathutils.Matrix(
                        ((1.0, 0.0, 0.0, 0.0),
                         (0.0, 0.0, 1.0, 0.0),
                         (0.0, -1.0, 0.0, 0.0),
                         (0.0, 0.0, 0.0, 1.0)))
                correction_matrix_local = axis_basis_change @ bone.bone.matrix_local
            else:
                correction_matrix_local = (
                    bone.parent.bone.matrix_local.inverted() @
                    bone.bone.matrix_local
                )

            transform = correction_matrix_local
        else:
            transform = mathutils.Matrix.Identity(4)
    else:
        transform = parent_inverse

    values = []
    for keyframe in keyframes:
        # Transform the data and build gltf control points
        value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform)
        if is_yup and not is_armature_animation:
            value = gltf2_blender_math.swizzle_yup(value, target_datapath)
        keyframe_value = gltf2_blender_math.mathutils_to_gltf(value)

        if keyframe.in_tangent is not None:
            # we can directly transform the tangent as it currently is represented by a control point
            in_tangent = gltf2_blender_math.transform(keyframe.in_tangent, target_datapath, transform)
            if is_yup and blender_object_if_armature is None:
                in_tangent = gltf2_blender_math.swizzle_yup(in_tangent, target_datapath)
            # the tangent in glTF is relative to the keyframe value
            if not isinstance(value, list):
                in_tangent = value - in_tangent
            else:
                in_tangent = [value[i] - in_tangent[i] for i in range(len(value))]
            keyframe_value = gltf2_blender_math.mathutils_to_gltf(in_tangent) + keyframe_value  # append

        if keyframe.out_tangent is not None:
            # we can directly transform the tangent as it currently is represented by a control point
            out_tangent = gltf2_blender_math.transform(keyframe.out_tangent, target_datapath, transform)
            if is_yup and blender_object_if_armature is None:
                out_tangent = gltf2_blender_math.swizzle_yup(out_tangent, target_datapath)
            # the tangent in glTF is relative to the keyframe value
            if not isinstance(value, list):
                out_tangent = value - out_tangent
            else:
                out_tangent = [value[i] - out_tangent[i] for i in range(len(value))]
            keyframe_value = keyframe_value + gltf2_blender_math.mathutils_to_gltf(out_tangent)  # append

        values += keyframe_value

    # store the keyframe data in a binary buffer
    component_type = gltf2_io_constants.ComponentType.Float
    if get_target_property_name(target_datapath) == "value":
        # channels with 'weight' targets must have scalar accessors
        data_type = gltf2_io_constants.DataType.Scalar
    else:
        data_type = gltf2_io_constants.DataType.vec_type_from_num(len(keyframes[0].value))

    return gltf2_io.Accessor(
        buffer_view=gltf2_io_binary_data.BinaryData.from_list(values, component_type),
        byte_offset=None,
        component_type=component_type,
        count=len(values) // gltf2_io_constants.DataType.num_elements(data_type),
        extensions=None,
        extras=None,
        max=None,
        min=None,
        name=None,
        normalized=None,
        sparse=None,
        type=data_type
    )
Exemplo n.º 11
0
def gather_keyframes(blender_object_if_armature: typing.Optional[
    bpy.types.Object], channels: typing.Tuple[bpy.types.FCurve],
                     non_keyed_values: typing.Tuple[typing.Optional[float]],
                     bake_bone: typing.Union[str, None],
                     bake_channel: typing.Union[str, None], bake_range_start,
                     bake_range_end, action_name: str, driver_obj,
                     export_settings) -> typing.List[Keyframe]:
    """Convert the blender action groups' fcurves to keyframes for use in glTF."""
    if bake_bone is None and driver_obj is None:
        # Find the start and end of the whole action group
        # Note: channels has some None items only for SK if some SK are not animated
        ranges = [
            channel.range() for channel in channels if channel is not None
        ]

        start_frame = min([
            channel.range()[0] for channel in channels if channel is not None
        ])
        end_frame = max([
            channel.range()[1] for channel in channels if channel is not None
        ])
    else:
        start_frame = bake_range_start
        end_frame = bake_range_end

    keyframes = []
    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 and driver_obj is None:
            if bake_bone is 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 = blender_object_if_armature.pose.bones[
                    bake_bone]
        else:
            pose_bone_if_armature = None

        # sample all frames
        frame = start_frame
        step = export_settings['gltf_frame_step']
        while frame <= end_frame:
            key = Keyframe(channels, frame, bake_channel)
            if isinstance(pose_bone_if_armature, bpy.types.PoseBone):

                mat = get_bone_matrix(blender_object_if_armature, channels,
                                      bake_bone, bake_channel,
                                      bake_range_start, bake_range_end,
                                      action_name, frame, step)
                trans, rot, scale = mat.decompose()

                if bake_channel is None:
                    target_property = channels[0].data_path.split('.')[-1]
                else:
                    target_property = bake_channel
                key.value = {
                    "location": trans,
                    "rotation_axis_angle": rot,
                    "rotation_euler": rot,
                    "rotation_quaternion": rot,
                    "scale": scale
                }[target_property]
            else:
                if driver_obj is None:
                    # Note: channels has some None items only for SK if some SK are not animated
                    key.value = [
                        c.evaluate(frame) for c in channels if c is not None
                    ]
                    complete_key(key, non_keyed_values)
                else:
                    key.value = get_sk_driver_values(driver_obj, frame,
                                                     channels)
                    complete_key(key, non_keyed_values)
            keyframes.append(key)
            frame += step
    else:
        # Just use the keyframes as they are specified in blender
        # Note: channels has some None items only for SK if some SK are not animated
        frames = [
            keyframe.co[0]
            for keyframe in [c for c in channels
                             if c is not None][0].keyframe_points
        ]
        # some weird files have duplicate frame at same time, removed them
        frames = sorted(set(frames))
        for i, frame in enumerate(frames):
            key = Keyframe(channels, frame, bake_channel)
            # key.value = [c.keyframe_points[i].co[0] for c in action_group.channels]
            key.value = [c.evaluate(frame) for c in channels if c is not None]
            # Complete key with non keyed values, if needed
            if len([c for c in channels if c is not None
                    ]) != key.get_target_len():
                complete_key(key, non_keyed_values)

            # compute tangents for cubic spline interpolation
            if [c for c in channels if c is not None
                ][0].keyframe_points[0].interpolation == "BEZIER":
                # Construct the in tangent
                if frame == frames[0]:
                    # start in-tangent should become all zero
                    key.set_first_tangent()
                else:
                    # otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
                    # use a point at t-1 to define the tangent. This allows the tangent control point to be transformed
                    # normally
                    key.in_tangent = [
                        c.keyframe_points[i].co[1] +
                        ((c.keyframe_points[i].co[1] -
                          c.keyframe_points[i].handle_left[1]) /
                         (frame - frames[i - 1])) for c in channels
                        if c is not None
                    ]
                # Construct the out tangent
                if frame == frames[-1]:
                    # end out-tangent should become all zero
                    key.set_last_tangent()
                else:
                    # otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
                    # use a point at t+1 to define the tangent. This allows the tangent control point to be transformed
                    # normally
                    key.out_tangent = [
                        c.keyframe_points[i].co[1] +
                        ((c.keyframe_points[i].handle_right[1] -
                          c.keyframe_points[i].co[1]) /
                         (frames[i + 1] - frame)) for c in channels
                        if c is not None
                    ]

                complete_key_tangents(key, non_keyed_values)

            keyframes.append(key)

    return keyframes
Exemplo n.º 12
0
def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
                             blender_object: bpy.types.Object,
                             bake_bone: typing.Union[str, None],
                             bake_channel: typing.Union[str, None],
                             bake_range_start, bake_range_end,
                             action_name: str, driver_obj,
                             export_settings) -> gltf2_io.AnimationSampler:

    if blender_object.animation_data and blender_object.animation_data.nla_tracks and export_settings[
            gltf2_blender_export_keys.FRAME_RANGE]:
        # Attempt to adjust the bake range to match the nla track strip that matches the action_name.
        for track in blender_object.animation_data.nla_tracks:
            if not track.strips:
                continue
            for strip in track.strips:
                if strip.name == action_name:
                    bake_range_start = strip.frame_start
                    bake_range_end = strip.frame_end
                    break

    blender_object_if_armature = blender_object if blender_object.type == "ARMATURE" else None
    if blender_object_if_armature is not None and driver_obj is None:
        if bake_bone is 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 = blender_object.pose.bones[bake_bone]
    else:
        pose_bone_if_armature = None
    non_keyed_values = __gather_non_keyed_values(channels, blender_object,
                                                 blender_object_if_armature,
                                                 pose_bone_if_armature,
                                                 bake_channel, driver_obj,
                                                 export_settings)
    if blender_object.parent is not None:
        matrix_parent_inverse = blender_object.matrix_parent_inverse.copy(
        ).freeze()
    else:
        matrix_parent_inverse = mathutils.Matrix.Identity(4).freeze()

    sampler = gltf2_io.AnimationSampler(
        extensions=__gather_extensions(channels, blender_object_if_armature,
                                       export_settings, bake_bone,
                                       bake_channel),
        extras=__gather_extras(channels, blender_object_if_armature,
                               export_settings, bake_bone, bake_channel),
        input=__gather_input(channels, blender_object_if_armature,
                             non_keyed_values, bake_bone, bake_channel,
                             bake_range_start, bake_range_end, action_name,
                             driver_obj, export_settings),
        interpolation=__gather_interpolation(channels,
                                             blender_object_if_armature,
                                             export_settings, bake_bone,
                                             bake_channel),
        output=__gather_output(channels, matrix_parent_inverse,
                               blender_object_if_armature, non_keyed_values,
                               bake_bone, bake_channel, bake_range_start,
                               bake_range_end, action_name, driver_obj,
                               export_settings))

    export_user_extensions('gather_animation_sampler_hook', export_settings,
                           sampler, channels, blender_object, bake_bone,
                           bake_channel, bake_range_start, bake_range_end,
                           action_name)

    return sampler
def gather_animation_sampler(channels: typing.Tuple[bpy.types.FCurve],
                             obj_uuid: str, bake_bone: typing.Union[str, None],
                             bake_channel: typing.Union[str, None],
                             bake_range_start, bake_range_end,
                             force_range: bool, action_name: str,
                             driver_obj_uuid, node_channel_is_animated: bool,
                             need_rotation_correction,
                             export_settings) -> gltf2_io.AnimationSampler:

    blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object
    is_armature = True if blender_object.type == "ARMATURE" else False
    blender_object_if_armature = blender_object if is_armature is True else None
    if is_armature is True and driver_obj_uuid is None:
        if bake_bone is 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 = blender_object.pose.bones[bake_bone]
    else:
        pose_bone_if_armature = None
    non_keyed_values = __gather_non_keyed_values(channels, blender_object,
                                                 blender_object_if_armature,
                                                 pose_bone_if_armature,
                                                 bake_channel, driver_obj_uuid,
                                                 export_settings)
    if blender_object.parent is not None:
        matrix_parent_inverse = blender_object.matrix_parent_inverse.copy(
        ).freeze()
    else:
        matrix_parent_inverse = mathutils.Matrix.Identity(4).freeze()

    input = __gather_input(channels, obj_uuid, is_armature, non_keyed_values,
                           bake_bone, bake_channel, bake_range_start,
                           bake_range_end, force_range, action_name,
                           driver_obj_uuid, node_channel_is_animated,
                           export_settings)

    if input is None:
        # After check, no need to animate this node for this channel
        return None

    sampler = gltf2_io.AnimationSampler(
        extensions=__gather_extensions(channels, blender_object_if_armature,
                                       export_settings, bake_bone,
                                       bake_channel),
        extras=__gather_extras(channels, blender_object_if_armature,
                               export_settings, bake_bone, bake_channel),
        input=input,
        interpolation=__gather_interpolation(channels,
                                             blender_object_if_armature,
                                             export_settings, bake_bone,
                                             bake_channel),
        output=__gather_output(channels, matrix_parent_inverse, obj_uuid,
                               is_armature, non_keyed_values, bake_bone,
                               bake_channel, bake_range_start, bake_range_end,
                               force_range, action_name, driver_obj_uuid,
                               node_channel_is_animated,
                               need_rotation_correction, export_settings))

    export_user_extensions('gather_animation_sampler_hook', export_settings,
                           sampler, channels, blender_object, bake_bone,
                           bake_channel, bake_range_start, bake_range_end,
                           action_name)

    return sampler
Exemplo n.º 14
0
def gather_keyframes(blender_object_if_armature: typing.Optional[
    bpy.types.Object], channels: typing.Tuple[bpy.types.FCurve],
                     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]

    start_frame = min([channel.range()[0] for channel in channels])
    end_frame = max([channel.range()[1] for channel in channels])

    keyframes = []
    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']
        while frame <= end_frame:
            key = Keyframe(channels, frame)
            if isinstance(pose_bone_if_armature, bpy.types.PoseBone):
                # we need to bake in the constraints
                bpy.context.scene.frame_set(frame)
                trans, rot, scale = pose_bone_if_armature.matrix_basis.decompose(
                )
                target_property = channels[0].data_path.split('.')[-1]
                key.value = {
                    "location": trans,
                    "rotation_axis_angle": rot,
                    "rotation_euler": rot,
                    "rotation_quaternion": rot,
                    "scale": scale
                }[target_property]

            else:
                key.value = [c.evaluate(frame) for c in channels]
            keyframes.append(key)
            frame += step
    else:
        # Just use the keyframes as they are specified in blender
        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]

            # compute tangents for cubic spline interpolation
            if channels[0].keyframe_points[0].interpolation == "BEZIER":
                # Construct the in tangent
                if frame == frames[0]:
                    # start in-tangent should become all zero
                    key.in_tangent = key.value
                else:
                    # otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
                    # use a point at t-1 to define the tangent. This allows the tangent control point to be transformed
                    # normally
                    key.in_tangent = [
                        c.keyframe_points[i].co[1] +
                        ((c.keyframe_points[i].co[1] -
                          c.keyframe_points[i].handle_left[1]) /
                         (frame - frames[i - 1])) for c in channels
                    ]
                # Construct the out tangent
                if frame == frames[-1]:
                    # end out-tangent should become all zero
                    key.out_tangent = key.value
                else:
                    # otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
                    # use a point at t+1 to define the tangent. This allows the tangent control point to be transformed
                    # normally
                    key.out_tangent = [
                        c.keyframe_points[i].co[1] +
                        ((c.keyframe_points[i].handle_right[1] -
                          c.keyframe_points[i].co[1]) /
                         (frames[i + 1] - frame)) for c in channels
                    ]

            keyframes.append(key)

    return keyframes
Exemplo n.º 15
0
def __get_channel_groups(blender_action: bpy.types.Action,
                         blender_object: bpy.types.Object, export_settings):
    targets = {}
    multiple_rotation_mode_detected = False
    for fcurve in blender_action.fcurves:
        # In some invalid files, channel hasn't any keyframes ... this channel need to be ignored
        if len(fcurve.keyframe_points) == 0:
            continue
        try:
            target_property = get_target_property_name(fcurve.data_path)
        except:
            gltf2_io_debug.print_console(
                "WARNING", "Invalid animation fcurve name on action {}".format(
                    blender_action.name))
            continue
        object_path = get_target_object_path(fcurve.data_path)

        # find the object affected by this action
        if not object_path:
            target = blender_object
        else:
            try:
                target = gltf2_blender_get.get_object_from_datapath(
                    blender_object, object_path)
                if blender_object.type == "MESH" and object_path.startswith(
                        "key_blocks"):
                    shape_key = blender_object.data.shape_keys.path_resolve(
                        object_path)
                    if shape_key.mute is True:
                        continue
                    target = blender_object.data.shape_keys
            except ValueError as e:
                # if the object is a mesh and the action target path can not be resolved, we know that this is a morph
                # animation.
                if blender_object.type == "MESH":
                    try:
                        shape_key = blender_object.data.shape_keys.path_resolve(
                            object_path)
                        if shape_key.mute is True:
                            continue
                        target = blender_object.data.shape_keys
                    except:
                        # Something is wrong, for example a bone animation is linked to an object mesh...
                        gltf2_io_debug.print_console(
                            "WARNING", "Animation target {} not found".format(
                                object_path))
                        continue
                else:
                    gltf2_io_debug.print_console(
                        "WARNING",
                        "Animation target {} not found".format(object_path))
                    continue

        # Detect that object or bone are not multiple keyed for euler and quaternion
        # Keep only the current rotation mode used by object / bone
        rotation, rotation_modes = get_rotation_modes(target_property)
        if rotation and target.rotation_mode not in rotation_modes:
            multiple_rotation_mode_detected = True
            continue

        # group channels by target object and affected property of the target
        target_properties = targets.get(target, {})
        channels = target_properties.get(target_property, [])
        channels.append(fcurve)
        target_properties[target_property] = channels
        targets[target] = target_properties

    groups = []
    for p in targets.values():
        groups += list(p.values())

    if multiple_rotation_mode_detected is True:
        gltf2_io_debug.print_console(
            "WARNING", "Multiple rotation mode detected for {}".format(
                blender_object.name))

    return map(tuple, groups)
def gather_animation_channels(
        obj_uuid: int, blender_action: bpy.types.Action,
        export_settings) -> typing.List[gltf2_io.AnimationChannel]:
    channels = []

    blender_object = export_settings['vtree'].nodes[obj_uuid].blender_object

    # First calculate range of animation for baking
    # This is need if user set 'Force sampling' and in case we need to bake
    bake_range_start = None
    bake_range_end = None
    force_range = False
    # If range is manually set, use it. Else, calculate it
    if blender_action.use_frame_range is True:
        bake_range_start = blender_action.frame_start
        bake_range_end = blender_action.frame_end
        force_range = True  # keyframe_points is read-only, we cant restrict here
    else:
        groups = __get_channel_groups(blender_action, blender_object,
                                      export_settings)
        # Note: channels has some None items only for SK if some SK are not animated
        for chans in groups:
            ranges = [
                channel.range() for channel in chans if channel is not None
            ]
            if bake_range_start is None:
                bake_range_start = min([
                    channel.range()[0] for channel in chans
                    if channel is not None
                ])
            else:
                bake_range_start = min(
                    bake_range_start,
                    min([
                        channel.range()[0] for channel in chans
                        if channel is not None
                    ]))
            if bake_range_end is None:
                bake_range_end = max([
                    channel.range()[1] for channel in chans
                    if channel is not None
                ])
            else:
                bake_range_end = max(
                    bake_range_end,
                    max([
                        channel.range()[1] for channel in chans
                        if channel is not None
                    ]))

    if blender_object.type == "ARMATURE" and export_settings[
            'gltf_force_sampling'] is True:
        # We have to store sampled animation data for every deformation bones

        # Check that there are some anim in this action
        if bake_range_start is None:
            return []

        # Then bake all bones
        bones_to_be_animated = []
        bones_uuid = export_settings["vtree"].get_all_bones(obj_uuid)
        bones_to_be_animated = [
            blender_object.pose.bones[
                export_settings["vtree"].nodes[b].blender_bone.name]
            for b in bones_uuid
        ]

        list_of_animated_bone_channels = []
        for channel_group in __get_channel_groups(blender_action,
                                                  blender_object,
                                                  export_settings):
            channel_group_sorted = __get_channel_group_sorted(
                channel_group, blender_object)
            list_of_animated_bone_channels.extend([
                (gltf2_blender_get.get_object_from_datapath(
                    blender_object, get_target_object_path(i.data_path)).name,
                 get_target_property_name(i.data_path)) for i in channel_group
            ])

        for bone in bones_to_be_animated:
            for p in ["location", "rotation_quaternion", "scale"]:
                channel = gather_animation_channel(
                    obj_uuid, (), export_settings, bone.name, p,
                    bake_range_start, bake_range_end, force_range,
                    blender_action.name, None, (bone.name, p)
                    in list_of_animated_bone_channels)
                if channel is not None:
                    channels.append(channel)

        # Retrieve animation on armature object itself, if any
        fcurves_armature = __gather_armature_object_channel_groups(
            blender_action, blender_object, export_settings)
        for channel_group in fcurves_armature:
            # No need to sort on armature, that can't have SK
            if len(channel_group) == 0:
                # Only errors on channels, ignoring
                continue
            channel = gather_animation_channel(obj_uuid, channel_group,
                                               export_settings, None, None,
                                               bake_range_start,
                                               bake_range_end, force_range,
                                               blender_action.name, None, True)
            if channel is not None:
                channels.append(channel)

        # Retrieve channels for drivers, if needed
        drivers_to_manage = gltf2_blender_gather_drivers.get_sk_drivers(
            obj_uuid, export_settings)
        for obj_driver_uuid, fcurves in drivers_to_manage:
            channel = gather_animation_channel(obj_uuid, fcurves,
                                               export_settings, None, None,
                                               bake_range_start,
                                               bake_range_end, force_range,
                                               blender_action.name,
                                               obj_driver_uuid, True)
            if channel is not None:
                channels.append(channel)

    else:
        done_paths = []
        for channel_group in __get_channel_groups(blender_action,
                                                  blender_object,
                                                  export_settings):
            channel_group_sorted = __get_channel_group_sorted(
                channel_group, blender_object)
            if len(channel_group_sorted) == 0:
                # Only errors on channels, ignoring
                continue
            channel = gather_animation_channel(obj_uuid, channel_group_sorted,
                                               export_settings, None, None,
                                               bake_range_start,
                                               bake_range_end, force_range,
                                               blender_action.name, None, True)
            if channel is not None:
                channels.append(channel)

            # Store already done channel path
            target = [c for c in channel_group_sorted
                      if c is not None][0].data_path.split('.')[-1]
            path = {
                "delta_location": "location",
                "delta_rotation_euler": "rotation_quaternion",
                "location": "location",
                "rotation_axis_angle": "rotation_quaternion",
                "rotation_euler": "rotation_quaternion",
                "rotation_quaternion": "rotation_quaternion",
                "scale": "scale",
                "value": "weights"
            }.get(target)
            if path is not None:
                done_paths.append(path)
        done_paths = list(set(done_paths))

        if export_settings['gltf_selected'] is True and export_settings[
                'vtree'].tree_troncated is True:
            start_frame = min(
                [v[0] for v in [a.frame_range for a in bpy.data.actions]])
            end_frame = max(
                [v[1] for v in [a.frame_range for a in bpy.data.actions]])
            to_be_done = ['location', 'rotation_quaternion', 'scale']
            to_be_done = [c for c in to_be_done if c not in done_paths]

            # In case of weight action, do nothing.
            # If there is only weight --> TRS is already managed at first
            if not (len(done_paths) == 1 and 'weights' in done_paths):
                for p in to_be_done:
                    channel = gather_animation_channel(
                        obj_uuid,
                        (),
                        export_settings,
                        None,
                        p,
                        start_frame,
                        end_frame,
                        force_range,
                        blender_action.name,
                        None,
                        False  #If Object is not animated, don't keep animation for this channel
                    )

                    if channel is not None:
                        channels.append(channel)

    # resetting driver caches
    gltf2_blender_gather_drivers.get_sk_driver_values.reset_cache()
    gltf2_blender_gather_drivers.get_sk_drivers.reset_cache()
    # resetting bone caches
    gltf2_blender_gather_animation_sampler_keyframes.get_bone_matrix.reset_cache(
    )

    return channels
def gather_keyframes(blender_obj_uuid: str,
                     is_armature: bool,
                     channels: typing.Tuple[bpy.types.FCurve],
                     non_keyed_values: typing.Tuple[typing.Optional[float]],
                     bake_bone: typing.Union[str, None],
                     bake_channel: typing.Union[str, None],
                     bake_range_start,
                     bake_range_end,
                     force_range: bool,
                     action_name: str,
                     driver_obj_uuid,
                     node_channel_is_animated: bool,
                     export_settings
                     ) -> typing.Tuple[typing.List[Keyframe], bool]:
    """Convert the blender action groups' fcurves to keyframes for use in glTF."""

    blender_object_if_armature = export_settings['vtree'].nodes[blender_obj_uuid].blender_object if is_armature is True is not None else None
    blender_obj_uuid_if_armature = blender_obj_uuid if is_armature is True else None

    if force_range is True:
        start_frame = bake_range_start
        end_frame = bake_range_end
    else:
        if bake_bone is None and driver_obj_uuid is None:
            # Find the start and end of the whole action group
            # Note: channels has some None items only for SK if some SK are not animated
            ranges = [channel.range() for channel in channels if channel is not None]

            if len(channels) != 0:
                start_frame = min([channel.range()[0] for channel in channels  if channel is not None])
                end_frame = max([channel.range()[1] for channel in channels  if channel is not None])
            else:
                start_frame = bake_range_start
                end_frame = bake_range_end
        else:
            start_frame = bake_range_start
            end_frame = bake_range_end

    keyframes = []
    baking_is_needed = needs_baking(blender_object_if_armature, channels, export_settings)
    if baking_is_needed:
        # Bake the animation, by evaluating the animation for all frames

        if blender_object_if_armature is not None and driver_obj_uuid is None:
            if bake_bone is 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 = blender_object_if_armature.pose.bones[bake_bone]
        else:
            pose_bone_if_armature = None

        # sample all frames
        frame = start_frame
        step = export_settings['gltf_frame_step']
        while frame <= end_frame:
            key = Keyframe(channels, frame, bake_channel)
            if isinstance(pose_bone_if_armature, bpy.types.PoseBone):

                mat = get_bone_matrix(
                    blender_obj_uuid_if_armature,
                    channels,
                    bake_bone,
                    bake_channel,
                    bake_range_start,
                    bake_range_end,
                    action_name,
                    frame,
                    step,
                    export_settings
                )
                trans, rot, scale = mat.decompose()

                if bake_channel is None:
                    target_property = channels[0].data_path.split('.')[-1]
                else:
                    target_property = bake_channel
                key.value = {
                    "location": trans,
                    "rotation_axis_angle": rot,
                    "rotation_euler": rot,
                    "rotation_quaternion": rot,
                    "scale": scale
                }[target_property]
            else:
                if driver_obj_uuid is None:
                    # If channel is TRS, we bake from world matrix, else this is SK
                    if len(channels) != 0:
                        target = [c for c in channels if c is not None][0].data_path.split('.')[-1]
                    else:
                        target = bake_channel
                    if target == "value": #SK
                        # Note: channels has some None items only for SK if some SK are not animated
                        key.value = [c.evaluate(frame) for c in channels if c is not None]
                        complete_key(key, non_keyed_values)
                    else:

                        mat = get_object_matrix(blender_obj_uuid,
                                action_name,
                                bake_range_start,
                                bake_range_end,
                                frame,
                                step,
                                export_settings)

                        trans, rot, sca = mat.decompose()
                        key.value_total = {
                            "location": trans,
                            "rotation_axis_angle": [rot.to_axis_angle()[1], rot.to_axis_angle()[0][0], rot.to_axis_angle()[0][1], rot.to_axis_angle()[0][2]],
                            "rotation_euler": rot.to_euler(),
                            "rotation_quaternion": rot,
                            "scale": sca
                        }[target]
                else:
                    key.value = get_sk_driver_values(driver_obj_uuid, frame, channels, export_settings)
                    complete_key(key, non_keyed_values)
            keyframes.append(key)
            frame += step
    else:
        # Just use the keyframes as they are specified in blender
        # Note: channels has some None items only for SK if some SK are not animated
        frames = [keyframe.co[0] for keyframe in [c for c in channels if c is not None][0].keyframe_points]
        # some weird files have duplicate frame at same time, removed them
        frames = sorted(set(frames))
        if force_range is True:
            frames = [f for f in frames if f >= bake_range_start and f <= bake_range_end]
        for i, frame in enumerate(frames):
            key = Keyframe(channels, frame, bake_channel)
            # key.value = [c.keyframe_points[i].co[0] for c in action_group.channels]
            key.value = [c.evaluate(frame) for c in channels if c is not None]
            # Complete key with non keyed values, if needed
            if len([c for c in channels if c is not None]) != key.get_target_len():
                complete_key(key, non_keyed_values)

            # compute tangents for cubic spline interpolation
            if [c for c in channels if c is not None][0].keyframe_points[0].interpolation == "BEZIER":
                # Construct the in tangent
                if frame == frames[0]:
                    # start in-tangent should become all zero
                    key.set_first_tangent()
                else:
                    # otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
                    # use a point at t-1 to define the tangent. This allows the tangent control point to be transformed
                    # normally
                    key.in_tangent = [
                        c.keyframe_points[i].co[1] + ((c.keyframe_points[i].co[1] - c.keyframe_points[i].handle_left[1]
                                                       ) / (frame - frames[i - 1]))
                        for c in channels if c is not None
                    ]
                # Construct the out tangent
                if frame == frames[-1]:
                    # end out-tangent should become all zero
                    key.set_last_tangent()
                else:
                    # otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
                    # use a point at t+1 to define the tangent. This allows the tangent control point to be transformed
                    # normally
                    key.out_tangent = [
                        c.keyframe_points[i].co[1] + ((c.keyframe_points[i].handle_right[1] - c.keyframe_points[i].co[1]
                                                       ) / (frames[i + 1] - frame))
                        for c in channels if c is not None
                    ]

                complete_key_tangents(key, non_keyed_values)

            keyframes.append(key)

    if not export_settings[gltf2_blender_export_keys.OPTIMIZE_ANIMS]:
        return (keyframes, baking_is_needed)

    # For armature only
    # Check if all values are the same
    # In that case, if there is no real keyframe on this channel for this given bone,
    # We can ignore this keyframes
    # if there are some fcurve, we can keep only 2 keyframes, first and last
    if blender_object_if_armature is not None:
        cst = fcurve_is_constant(keyframes)

        if node_channel_is_animated is True: # fcurve on this bone for this property
             # Keep animation, but keep only 2 keyframes if data are not changing
             return ([keyframes[0], keyframes[-1]], baking_is_needed) if cst is True and len(keyframes) >= 2 else (keyframes, baking_is_needed)
        else: # bone is not animated (no fcurve)
            # Not keeping if not changing property
            return (None, baking_is_needed) if cst is True else (keyframes, baking_is_needed)
    else:
        # For objects, if all values are the same, we keep only first and last
        cst = fcurve_is_constant(keyframes)
        if node_channel_is_animated is True:
            return ([keyframes[0], keyframes[-1]], baking_is_needed) if cst is True and len(keyframes) >= 2 else (keyframes, baking_is_needed)
        else:
            # baked object (selected but not animated)
            return (None, baking_is_needed) if cst is True else (keyframes, baking_is_needed)

    return (keyframes, baking_is_needed)
def gather_keyframes(blender_object_if_armature: typing.Optional[bpy.types.Object],
                     channels: typing.Tuple[bpy.types.FCurve],
                     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]

    start_frame = min([channel.range()[0] for channel in channels])
    end_frame = max([channel.range()[1] for channel in channels])

    keyframes = []
    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']
        while frame <= end_frame:
            key = Keyframe(channels, frame)
            if isinstance(pose_bone_if_armature, bpy.types.PoseBone):
                # we need to bake in the constraints
                bpy.context.scene.frame_set(frame)
                trans, rot, scale = pose_bone_if_armature.matrix_basis.decompose()
                target_property = channels[0].data_path.split('.')[-1]
                key.value = {
                    "location": trans,
                    "rotation_axis_angle": rot,
                    "rotation_euler": rot,
                    "rotation_quaternion": rot,
                    "scale": scale
                }[target_property]

            else:
                key.value = [c.evaluate(frame) for c in channels]
            keyframes.append(key)
            frame += step
    else:
        # Just use the keyframes as they are specified in blender
        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]

            # compute tangents for cubic spline interpolation
            if channels[0].keyframe_points[0].interpolation == "BEZIER":
                # Construct the in tangent
                if frame == frames[0]:
                    # start in-tangent should become all zero
                    key.in_tangent = key.value
                else:
                    # otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
                    # use a point at t-1 to define the tangent. This allows the tangent control point to be transformed
                    # normally
                    key.in_tangent = [
                        c.keyframe_points[i].co[1] + ((c.keyframe_points[i].co[1] - c.keyframe_points[i].handle_left[1]
                                                       ) / (frame - frames[i - 1]))
                        for c in channels
                    ]
                # Construct the out tangent
                if frame == frames[-1]:
                    # end out-tangent should become all zero
                    key.out_tangent = key.value
                else:
                    # otherwise construct an in tangent coordinate from the keyframes control points. We intermediately
                    # use a point at t+1 to define the tangent. This allows the tangent control point to be transformed
                    # normally
                    key.out_tangent = [
                        c.keyframe_points[i].co[1] + ((c.keyframe_points[i].handle_right[1] - c.keyframe_points[i].co[1]
                                                       ) / (frames[i + 1] - frame))
                        for c in channels
                    ]

            keyframes.append(key)

    return keyframes
def gather_animation_channels(
        blender_action: bpy.types.Action, blender_object: bpy.types.Object,
        export_settings) -> typing.List[gltf2_io.AnimationChannel]:
    channels = []

    # First calculate range of animation for baking
    # This is need if user set 'Force sampling' and in case we need to bake
    bake_range_start = None
    bake_range_end = None
    groups = __get_channel_groups(blender_action, blender_object,
                                  export_settings)
    # Note: channels has some None items only for SK if some SK are not animated
    for chans in groups:
        ranges = [channel.range() for channel in chans if channel is not None]
        if bake_range_start is None:
            bake_range_start = min([
                channel.range()[0] for channel in chans if channel is not None
            ])
        else:
            bake_range_start = min(
                bake_range_start,
                min([
                    channel.range()[0] for channel in chans
                    if channel is not None
                ]))
        if bake_range_end is None:
            bake_range_end = max([
                channel.range()[1] for channel in chans if channel is not None
            ])
        else:
            bake_range_end = max(
                bake_range_end,
                max([
                    channel.range()[1] for channel in chans
                    if channel is not None
                ]))

    if blender_object.type == "ARMATURE" and export_settings[
            'gltf_force_sampling'] is True:
        # We have to store sampled animation data for every deformation bones

        # Check that there are some anim in this action
        if bake_range_start is None:
            return []

        # Then bake all bones
        bones_to_be_animated = []
        if export_settings["gltf_def_bones"] is False:
            bones_to_be_animated = blender_object.data.bones
        else:
            bones_to_be_animated, _, _ = gltf2_blender_gather_skins.get_bone_tree(
                None, blender_object)
            bones_to_be_animated = [
                blender_object.pose.bones[b.name] for b in bones_to_be_animated
            ]

        list_of_animated_bone_channels = []
        for channel_group in __get_channel_groups(blender_action,
                                                  blender_object,
                                                  export_settings):
            channel_group_sorted = __get_channel_group_sorted(
                channel_group, blender_object)
            list_of_animated_bone_channels.extend([
                (gltf2_blender_get.get_object_from_datapath(
                    blender_object, get_target_object_path(i.data_path)).name,
                 get_target_property_name(i.data_path)) for i in channel_group
            ])

        for bone in bones_to_be_animated:
            for p in ["location", "rotation_quaternion", "scale"]:
                channel = __gather_animation_channel(
                    (), blender_object, export_settings, bone.name, p,
                    bake_range_start, bake_range_end, blender_action.name,
                    None, (bone.name, p) in list_of_animated_bone_channels)
                if channel is not None:
                    channels.append(channel)

        # Retrieve animation on armature object itself, if any
        fcurves_armature = __gather_armature_object_channel_groups(
            blender_action, blender_object, export_settings)
        for channel_group in fcurves_armature:
            # No need to sort on armature, that can't have SK
            if len(channel_group) == 0:
                # Only errors on channels, ignoring
                continue
            channel = __gather_animation_channel(channel_group, blender_object,
                                                 export_settings, None, None,
                                                 bake_range_start,
                                                 bake_range_end,
                                                 blender_action.name, None,
                                                 True)
            if channel is not None:
                channels.append(channel)

        # Retrieve channels for drivers, if needed
        obj_driver = blender_object.proxy if blender_object.proxy else blender_object
        drivers_to_manage = gltf2_blender_gather_drivers.get_sk_drivers(
            obj_driver)
        for obj, fcurves in drivers_to_manage:
            channel = __gather_animation_channel(fcurves, blender_object,
                                                 export_settings, None, None,
                                                 bake_range_start,
                                                 bake_range_end,
                                                 blender_action.name, obj,
                                                 False)
            if channel is not None:
                channels.append(channel)

    else:
        for channel_group in __get_channel_groups(blender_action,
                                                  blender_object,
                                                  export_settings):
            channel_group_sorted = __get_channel_group_sorted(
                channel_group, blender_object)
            if len(channel_group_sorted) == 0:
                # Only errors on channels, ignoring
                continue
            channel = __gather_animation_channel(
                channel_group_sorted, blender_object, export_settings, None,
                None, bake_range_start, bake_range_end, blender_action.name,
                None, False)
            if channel is not None:
                channels.append(channel)

    # resetting driver caches
    gltf2_blender_gather_drivers.get_sk_driver_values.reset_cache()
    gltf2_blender_gather_drivers.get_sk_drivers.reset_cache()
    # resetting bone caches
    gltf2_blender_gather_animation_sampler_keyframes.get_bone_matrix.reset_cache(
    )

    return channels
def __gather_output(channels: typing.Tuple[bpy.types.FCurve],
                    parent_inverse,
                    blender_object_if_armature: typing.Optional[bpy.types.Object],
                    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,
                                                                                  export_settings)

    target_datapath = channels[0].data_path

    transform = mathutils.Matrix.Identity(4)

    is_yup = export_settings[gltf2_blender_export_keys.YUP]

    # bone animations need to be handled differently as they are in a different coordinate system
    object_path = get_target_object_path(target_datapath)
    is_armature_animation = blender_object_if_armature is not None and object_path != ""

    if is_armature_animation:
        bone = gltf2_blender_get.get_object_from_datapath(blender_object_if_armature, object_path)
        if isinstance(bone, bpy.types.PoseBone):
            if bone.parent is None:
                axis_basis_change = mathutils.Matrix.Identity(4)
                if export_settings[gltf2_blender_export_keys.YUP]:
                    axis_basis_change = mathutils.Matrix(
                        ((1.0, 0.0, 0.0, 0.0),
                         (0.0, 0.0, 1.0, 0.0),
                         (0.0, -1.0, 0.0, 0.0),
                         (0.0, 0.0, 0.0, 1.0)))
                correction_matrix_local = gltf2_blender_math.multiply(axis_basis_change, bone.bone.matrix_local)
            else:
                correction_matrix_local = gltf2_blender_math.multiply(
                    bone.parent.bone.matrix_local.inverted(), bone.bone.matrix_local)

            transform = correction_matrix_local

    values = []
    for keyframe in keyframes:
        # Transform the data and build gltf control points
        value = gltf2_blender_math.transform(keyframe.value, target_datapath, transform)
        if is_yup and not is_armature_animation:
            value = gltf2_blender_math.swizzle_yup(value, target_datapath)
        keyframe_value = gltf2_blender_math.mathutils_to_gltf(value)

        if keyframe.in_tangent is not None:
            # we can directly transform the tangent as it currently is represented by a control point
            in_tangent = gltf2_blender_math.transform(keyframe.in_tangent, target_datapath, transform)
            if is_yup and blender_object_if_armature is None:
                in_tangent = gltf2_blender_math.swizzle_yup(in_tangent, target_datapath)
            # the tangent in glTF is relative to the keyframe value
            in_tangent = value - in_tangent if not isinstance(value, list) else [value[0] - in_tangent[0]]
            keyframe_value = gltf2_blender_math.mathutils_to_gltf(in_tangent) + keyframe_value  # append

        if keyframe.out_tangent is not None:
            # we can directly transform the tangent as it currently is represented by a control point
            out_tangent = gltf2_blender_math.transform(keyframe.out_tangent, target_datapath, transform)
            if is_yup and blender_object_if_armature is None:
                out_tangent = gltf2_blender_math.swizzle_yup(out_tangent, target_datapath)
            # the tangent in glTF is relative to the keyframe value
            out_tangent = value - out_tangent if not isinstance(value, list) else [value[0] - out_tangent[0]]
            keyframe_value = keyframe_value + gltf2_blender_math.mathutils_to_gltf(out_tangent)  # append

        values += keyframe_value

    # store the keyframe data in a binary buffer
    component_type = gltf2_io_constants.ComponentType.Float
    if get_target_property_name(target_datapath) == "value":
        # channels with 'weight' targets must have scalar accessors
        data_type = gltf2_io_constants.DataType.Scalar
    else:
        data_type = gltf2_io_constants.DataType.vec_type_from_num(len(keyframes[0].value))

    return gltf2_io.Accessor(
        buffer_view=gltf2_io_binary_data.BinaryData.from_list(values, component_type),
        byte_offset=None,
        component_type=component_type,
        count=len(values) // gltf2_io_constants.DataType.num_elements(data_type),
        extensions=None,
        extras=None,
        max=None,
        min=None,
        name=None,
        normalized=None,
        sparse=None,
        type=data_type
    )