Пример #1
0
def __gather_animation(blender_action: bpy.types.Action,
                       blender_object: bpy.types.Object,
                       export_settings) -> typing.Optional[gltf2_io.Animation]:
    if not __filter_animation(blender_action, blender_object, export_settings):
        return None

    name = __gather_name(blender_action, blender_object, export_settings)
    try:
        animation = gltf2_io.Animation(
            channels=__gather_channels(blender_action, blender_object,
                                       export_settings),
            extensions=__gather_extensions(blender_action, blender_object,
                                           export_settings),
            extras=__gather_extras(blender_action, blender_object,
                                   export_settings),
            name=name,
            samplers=__gather_samplers(blender_action, blender_object,
                                       export_settings))
    except RuntimeError as error:
        print_console(
            "WARNING",
            "Animation '{}' could not be exported. Cause: {}".format(
                name, error))
        return None

    # To allow reuse of samplers in one animation,
    __link_samplers(animation, export_settings)

    if not animation.channels:
        return None

    export_user_extensions('gather_animation_hook', export_settings, animation,
                           blender_action, blender_object)

    return animation
Пример #2
0
def __merge_animations(blender_scene, animations, export_settings):
    scene_animation = gltf2_io.Animation(
        channels=__merge_channels(animations),
        extensions=__merge_extensions(animations),
        extras=__merge_extras(animations),
        name=blender_scene.name,
        samplers=__merge_samplers(animations))
    return scene_animation
Пример #3
0
def gather_animations(blender_object, export_settings):
    """
    Gather all animations which contribute to the objects property
    :param blender_object: The blender object which is animated
    :param export_settings:
    :return: A list of glTF2 animations
    """
    if not __filter_animation(blender_object, export_settings):
        return None

    return gltf2_io.Animation(
        channels=__gather_channels(blender_object, export_settings),
        extensions=__gather_extensions(blender_object, export_settings),
        extras=__gather_extras(blender_object, export_settings),
        name=__gather_name(blender_object, export_settings),
        samplers=__gather_samples(blender_object, export_settings))
Пример #4
0
def __gather_animation(blender_action: bpy.types.Action,
                       blender_object: bpy.types.Object,
                       export_settings) -> typing.Optional[gltf2_io.Animation]:
    if not __filter_animation(blender_action, blender_object, export_settings):
        return None

    animation = gltf2_io.Animation(
        channels=__gather_channels(blender_action, blender_object,
                                   export_settings),
        extensions=__gather_extensions(blender_action, blender_object,
                                       export_settings),
        extras=__gather_extras(blender_action, blender_object,
                               export_settings),
        name=__gather_name(blender_action, blender_object, export_settings),
        samplers=__gather_samplers(blender_action, blender_object,
                                   export_settings))

    # To allow reuse of samplers in one animation,
    __link_samplers(animation, export_settings)

    return animation
def __gather_animation(pre_anim, nodes, export_settings):
    anim_name, (frame_start, frame_end) = pre_anim
    print(f'exporting animation track {anim_name}')

    # Star all the tracks named anim_name. Objects without an anim_name
    # track are considered unanimated. Star the empty temp track for those.
    anim_nodes = []
    for node in nodes:
        if node.__blender_data[0] != 'OBJECT' and node.__blender_data[
                0] != 'BONE':
            continue
        ob = node.__blender_data[1]
        if not ob.animation_data:
            continue
        for track in ob.animation_data.nla_tracks:
            if track.name == anim_name:
                track.is_solo = True
                if ob.type == 'ARMATURE' and node.__blender_data[0] == 'BONE':
                    # only append bones that are animated in current anim
                    actions = __get_blender_actions(ob)
                    for action in actions:
                        if action[1] == anim_name:
                            for fcurve in action[0].fcurves:
                                bone_path = fcurve.data_path.rpartition('.')[0]
                                bone = ob.path_resolve(bone_path)
                                if node.name == bone.name:
                                    if node not in anim_nodes:
                                        anim_nodes.append(node)
                else:
                    anim_nodes.append(node)
                break
        else:
            if node.__blender_data[0] == 'OBJECT':
                node.__temp_nla_track.is_solo = True

    f_start = math.floor(frame_start)
    f_end = math.ceil(frame_end) + 1
    f_step = export_settings['gltf_frame_step']

    # Stores TRS values for each node at each frame
    data = {}
    data['translation'] = [[] for _node in nodes]
    data['rotation'] = [[] for _node in nodes]
    data['scale'] = [[] for _node in nodes]

    for f in range(f_start, f_end, f_step):
        bpy.context.scene.frame_set(f)
        for i, node in enumerate(anim_nodes):
            if node.__blender_data[0] == 'OBJECT':
                t, r, s = __get_gltf_trs_from_object(node.__blender_data[1],
                                                     export_settings)
            elif node.__blender_data[0] == 'BONE':
                arma_ob = node.__blender_data[1]
                pbone = arma_ob.pose.bones[node.__blender_data[2]]
                t, r, s = __get_gltf_trs_from_bone(pbone, export_settings)
            else:
                assert False
            data['translation'][i].append(t)
            data['rotation'][i].append(r)
            data['scale'][i].append(s)

    # Put it all together to get the glTF animation

    channels = []
    samplers = []

    for i, node in enumerate(anim_nodes):
        # Get paths used in the NLA track
        actions = __get_blender_actions(node.__blender_data[1])
        paths = []

        pathTypes = {
            'delta_location': 'translation',
            'delta_rotation_euler': 'rotation',
            'location': 'translation',
            'rotation_axis_angle': 'rotation',
            'rotation_euler': 'rotation',
            'rotation_quaternion': 'rotation',
            'scale': 'scale'
        }

        for action in actions:
            if action[1] == anim_name:
                for fcurve in action[0].fcurves:
                    data_path = fcurve.data_path
                    if node.__blender_data[0] == 'OBJECT':
                        paths.append(pathTypes.get(data_path))
                    else:  # for armatures
                        paths.append(
                            pathTypes.get(data_path.rpartition('.')[2]))

        for path in ['translation', 'rotation', 'scale']:
            if path in paths:
                sampler = gltf2_io.AnimationSampler(
                    input=__get_keyframe_accessor(f_start, f_end, f_step),
                    output=__encode_output_accessor(data[path][i], path),
                    interpolation='LINEAR',
                    extensions=None,
                    extras=None,
                )
                samplers.append(sampler)
                channel = gltf2_io.AnimationChannel(
                    sampler=len(samplers) - 1,
                    target=gltf2_io.AnimationChannelTarget(
                        node=node,
                        path=path,
                        extensions=None,
                        extras=None,
                    ),
                    extensions=None,
                    extras=None,
                )
                channels.append(channel)

    animation = gltf2_io.Animation(
        name=anim_name,
        channels=channels,
        samplers=samplers,
        extensions=None,
        extras=None,
    )

    return animation
def gather_animations(
    obj_uuid: int, tracks: typing.Dict[str, typing.List[int]], offset: int,
    export_settings
) -> typing.Tuple[typing.List[gltf2_io.Animation], typing.Dict[
        str, typing.List[int]]]:
    """
    Gather all animations which contribute to the objects property, and corresponding track names

    :param blender_object: The blender object which is animated
    :param export_settings:
    :return: A list of glTF2 animations and tracks
    """
    animations = []

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

    # Collect all 'actions' affecting this object. There is a direct mapping between blender actions and glTF animations
    blender_actions = __get_blender_actions(blender_object, export_settings)

    if len([a for a in blender_actions if a[2] == "OBJECT"]) == 0:
        # No TRS animation are found for this object.
        # But we need to bake, in case we export selection
        if export_settings[
                'gltf_selected'] is True and blender_object.type != "ARMATURE":
            channels = __gather_channels_baked(obj_uuid, export_settings)
            if channels is not None:
                animation = gltf2_io.Animation(
                    channels=channels,
                    extensions=None,  # as other animations
                    extras=
                    None,  # Because there is no animation to get extras from
                    name=blender_object.
                    name,  # Use object name as animation name
                    samplers=[])

                __link_samplers(animation, export_settings)
                if animation is not None:
                    animations.append(animation)
        elif export_settings[
                'gltf_selected'] is True and blender_object.type == "ARMATURE":
            # We need to bake all bones. Because some bone can have some constraints linking to
            # some other armature bones, for example
            #TODO
            pass

    current_action = None
    if blender_object.animation_data and blender_object.animation_data.action:
        current_action = blender_object.animation_data.action
    # Remove any solo (starred) NLA track. Restored after export
    solo_track = None
    if blender_object.animation_data:
        for track in blender_object.animation_data.nla_tracks:
            if track.is_solo:
                solo_track = track
                track.is_solo = False
                break

    # Remove any tweak mode. Restore after export
    if blender_object.animation_data:
        restore_tweak_mode = blender_object.animation_data.use_tweak_mode

    # Export all collected actions.
    for blender_action, track_name, on_type in blender_actions:

        # Set action as active, to be able to bake if needed
        if on_type == "OBJECT":  # Not for shapekeys!
            if blender_object.animation_data.action is None \
                    or (blender_object.animation_data.action.name != blender_action.name):
                if blender_object.animation_data.is_property_readonly(
                        'action'):
                    blender_object.animation_data.use_tweak_mode = False
                try:
                    blender_object.animation_data.action = blender_action
                except:
                    error = "Action is readonly. Please check NLA editor"
                    print_console(
                        "WARNING",
                        "Animation '{}' could not be exported. Cause: {}".
                        format(blender_action.name, error))
                    continue

        # No need to set active shapekeys animations, this is needed for bone baking

        animation = __gather_animation(obj_uuid, blender_action,
                                       export_settings)
        if animation is not None:
            animations.append(animation)

            # Store data for merging animation later
            if track_name is not None:  # Do not take into account animation not in NLA
                # Do not take into account default NLA track names
                if not (track_name.startswith("NlaTrack")
                        or track_name.startswith("[Action Stash]")):
                    if track_name not in tracks.keys():
                        tracks[track_name] = []
                    tracks[track_name].append(
                        offset + len(animations) -
                        1)  # Store index of animation in animations

    # Restore action status
    # TODO: do this in a finally
    if blender_object.animation_data:
        if blender_object.animation_data.action is not None:
            if current_action is None:
                # remove last exported action
                blender_object.animation_data.action = None
            elif blender_object.animation_data.action.name != current_action.name:
                # Restore action that was active at start of exporting
                blender_object.animation_data.action = current_action
        if solo_track is not None:
            solo_track.is_solo = True
        blender_object.animation_data.use_tweak_mode = restore_tweak_mode
    return animations, tracks