def get_bone_matrix(blender_obj_uuid_if_armature: typing.Optional[str],
                     channels: typing.Tuple[bpy.types.FCurve],
                     bake_bone: typing.Union[str, None],
                     bake_channel: typing.Union[str, None],
                     bake_range_start,
                     bake_range_end,
                     action_name: str,
                     current_frame: int,
                     step: int,
                     export_settings
                     ):

    blender_object_if_armature = export_settings['vtree'].nodes[blender_obj_uuid_if_armature].blender_object if blender_obj_uuid_if_armature is not None else None
    data = {}

    # Always using bake_range, because some bones may need to be baked,
    # even if user didn't request it

    start_frame = bake_range_start
    end_frame = bake_range_end


    frame = start_frame
    while frame <= end_frame:
        data[frame] = {}
        bpy.context.scene.frame_set(int(frame))
        bones = export_settings['vtree'].get_all_bones(blender_obj_uuid_if_armature)

        for bone_uuid in bones:
            blender_bone = export_settings['vtree'].nodes[bone_uuid].blender_bone

            if export_settings['vtree'].nodes[bone_uuid].parent_uuid is not None and export_settings['vtree'].nodes[export_settings['vtree'].nodes[bone_uuid].parent_uuid].blender_type == VExportNode.BONE:
                blender_bone_parent = export_settings['vtree'].nodes[export_settings['vtree'].nodes[bone_uuid].parent_uuid].blender_bone
                rest_mat = blender_bone_parent.bone.matrix_local.inverted_safe() @ blender_bone.bone.matrix_local
                matrix = rest_mat.inverted_safe() @ blender_bone_parent.matrix.inverted_safe() @ blender_bone.matrix
            else:
                if blender_bone.parent is None:
                    matrix = blender_bone.bone.matrix_local.inverted_safe() @ blender_bone.matrix
                else:
                    # Bone has a parent, but in export, after filter, is at root of armature
                    matrix = blender_bone.matrix.copy()

            data[frame][blender_bone.name] = matrix


        # If some drivers must be evaluated, do it here, to avoid to have to change frame by frame later
        drivers_to_manage = get_sk_drivers(blender_obj_uuid_if_armature, export_settings)
        for dr_obj_uuid, dr_fcurves in drivers_to_manage:
            vals = get_sk_driver_values(dr_obj_uuid, frame, dr_fcurves, export_settings)

        frame += step

    return data
def get_bone_matrix(
        blender_object_if_armature: typing.Optional[bpy.types.Object],
        channels: typing.Tuple[bpy.types.FCurve],
        bake_bone: typing.Union[str, None],
        bake_channel: typing.Union[str, None], bake_range_start,
        bake_range_end, action_name: str, current_frame: int, step: int):

    data = {}

    # Always using bake_range, because some bones may need to be baked,
    # even if user didn't request it

    start_frame = bake_range_start
    end_frame = bake_range_end

    frame = start_frame
    while frame <= end_frame:
        data[frame] = {}
        # we need to bake in the constraints
        bpy.context.scene.frame_set(frame)
        for pbone in blender_object_if_armature.pose.bones:
            if bake_bone is None:
                matrix = pbone.matrix_basis.copy()
            else:
                if (pbone.bone.use_inherit_rotation == False
                        or pbone.bone.inherit_scale != "FULL"
                    ) and pbone.parent != None:
                    rest_mat = (pbone.parent.bone.matrix_local.inverted_safe()
                                @ pbone.bone.matrix_local)
                    matrix = (
                        rest_mat.inverted_safe()
                        @ pbone.parent.matrix.inverted_safe() @ pbone.matrix)
                else:
                    matrix = pbone.matrix
                    matrix = blender_object_if_armature.convert_space(
                        pose_bone=pbone,
                        matrix=matrix,
                        from_space='POSE',
                        to_space='LOCAL')

            data[frame][pbone.name] = matrix

        # If some drivers must be evaluated, do it here, to avoid to have to change frame by frame later
        obj_driver = blender_object_if_armature.proxy if blender_object_if_armature.proxy else blender_object_if_armature
        drivers_to_manage = get_sk_drivers(obj_driver)
        for dr_obj, dr_fcurves in drivers_to_manage:
            vals = get_sk_driver_values(dr_obj, frame, dr_fcurves)

        frame += step

    return data
示例#3
0
def get_bone_matrix(
        blender_object_if_armature: typing.Optional[bpy.types.Object],
        channels: typing.Tuple[bpy.types.FCurve],
        bake_bone: typing.Union[str, None],
        bake_channel: typing.Union[str, None], bake_range_start,
        bake_range_end, action_name: str, current_frame: int, step: int):

    data = {}

    # Always using bake_range, because some bones may need to be baked,
    # even if user didn't request it

    start_frame = bake_range_start
    end_frame = bake_range_end

    frame = start_frame
    while frame <= end_frame:
        data[frame] = {}
        # we need to bake in the constraints
        bpy.context.scene.frame_set(frame)
        for pbone in blender_object_if_armature.pose.bones:
            if bake_bone is None:
                matrix = pbone.matrix_basis
            else:
                matrix = pbone.matrix
                if bpy.app.version < (2, 80, 0):
                    matrix = blender_object_if_armature.convert_space(
                        pbone, matrix, 'POSE', 'LOCAL')
                else:
                    matrix = blender_object_if_armature.convert_space(
                        pose_bone=pbone,
                        matrix=matrix,
                        from_space='POSE',
                        to_space='LOCAL')
            data[frame][pbone.name] = matrix

        # If some drivers must be evaluated, do it here, to avoid to have to change frame by frame later
        drivers_to_manage = get_sk_drivers(blender_object_if_armature)
        for dr_obj, dr_fcurves in drivers_to_manage:
            vals = get_sk_driver_values(dr_obj, frame, dr_fcurves)

        frame += step

    return data
示例#4
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
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)