Motionbuilder Python: Batch export needs FBApplication Instance

I am running Motionbuilder 2020 and I am trying to export a file to an FBX with set start and end frames. So in searching how to do this I came a cross the FBBatchExport call which seems to be perfect, but I am tripping up trying to get an instance of FBApplication.

from pyfbsdk import *

def get_selected_models():
    selected_models = FBModelList()

    FBGetSelectedModels(selected_models, None, True, True)
    return selected_models


def get_selected_take():
    selected_takes = []
    for take in FBSystem().Scene.Takes:
        if take.Selected:
            selected_takes.append(take)
    return selected_takes



selected_take = get_selected_take()
batch_options = FBBatchOptions()
selected_models = get_selected_models()
batch_options.OutputDirectory = r"E:\FBXExports"    
app = FBApplication()

FBApplication.FileExportBatch(app,"test", selected_take, batch_options, selected_models)

Running this gives me:

Boost.Python.ArgumentError: Python argument types in
FBApplication.FileExportBatch(FBApplication, str, list, FBBatchOptions, FBModelList)
did not match C++ signature:
FileExportBatch(class PYFBSDK::FBApplication_Wrapper {lvalue}, char const * __ptr64, class PYFBSDK::FBTake_Wrapper {lvalue}, class PYFBSDK::FBBatchOptions_Wrapper {lvalue}, class PYFBSDK::FBModelList_Wrapper {lvalue})

So I am assuming that app = FBApplication() is not enough or just incorrect for getting the application instance. I am Stumped and would love any help here to get this working.

Hey TheBeaver,

If you want to set your start and end frames. These will help:

def get_take_start_and_endframes(fcurve) -> tuple:
    """
    
    Description: Method will return a take start and end frame numbers in the form of a tuple int!
    
    Args: FBFCurve, The object's fcurve.
    
    Return: (start, end) tuple int values or None if no keys are present.
    
    """
    last_key_index = len(fcurve.Keys)-1 # -1 is due to frame will starts at 0
    if last_key_index > 0:
        start_key = fcurve.Keys[0]
        end_key = fcurve.Keys[last_key_index]
        start_frame = start_key.Time.GetFrame()
        end_frame = end_key.Time.GetFrame()        
        print("Start frame: {0} and End frame {1}".format(start_frame, end_frame))            
        return (start_frame, end_frame)
    else:
        curve_name = fcurve.Name
        # print(f"No keys found for {curve_name}")             
        print("Start frame: 0 and End frame 0")   
        return (0, 0)
    
    
def set_take_start_and_endframes(start_frame: int, end_frame: int, fcurve) -> None:
    """
    
    Description: Sets start and end frames for a single take!
    
    Args: 
        fcurve = FBFCurve
        start_frame = int, 
        end_frame = int, 
    
    """
    FBPlayerControl().LoopStart = FBTime(0,0,0,start_frame)
    FBPlayerControl().LoopStop = FBTime(0,0,0,end_frame)

Here is the export. It wont work because it is using my classes (mainly for building my export path). You can pick out the useful bits of code to use.

def export_takes(all_scene_takes: list):
    """        
    Description: Exports takes. This feature includes set fbx settings!
    
    Note: FBX settings includes all scene content!
    
    Param: all_scene_takes, a list of FBTakes.
                        
    """
    #Define lSystem
    lSystem = FBSystem()
    
    app = FBApplication()
    # Create an FBFbxOptions object initialized to load data.
    fbx_options = FBFbxOptions(False) # True if load/merge fbx file options and False for saving files!
    # Below, Appends all the data in the file, which are appended
    # into the current scene. The second parameter (if False) of SetAll() ignores
    # any animation data contained in the file.
    fbx_options.SetAll(FBElementAction.kFBElementActionSave, True) 
    fbx_options.FileFormatAndVersion = FBFileFormatAndVersion.kFBFBX2014_2015 # FBX save version 2014_2015 
    fbx_options.EmbedMedia = False   # Dont save media files 
   
    print("\nExporting the following takes to Source:\n")
    # Iterate through each take to export!
    for take in lSystem.Scene.Takes:
        for index in range( fbx_options.GetTakeCount( ) ):
            if fbx_options.GetTakeName( index ) == take.Name:                    
                fbx_options.SetTakeSelect( index, True )
            else:
                fbx_options.SetTakeSelect( index, False )
                
        class_savepath = util.MiscUtils()
        take_source_path = class_savepath.get_savepath_argument(take)
        #If take SavePath property exists then do below! 
        # Note: SavePath argument has the take merge source path location to save when exporting!
        if take_source_path:
            #Merge all object in the scene to the BaseAnimation layer and delete the empty layers
            FBSystem().CurrentTake.MergeLayers(FBAnimationLayerMergeOptions.kFBAnimLayerMerge_AllLayers_CompleteScene, True, FBMergeLayerMode.kFBMergeLayerModeAutomatic)
            take_name = take.Name
            take_filepath = f"{take_source_path}/{take_name}.fbx"
                # Do this to export take!
            if take_filepath:
                app.FileSave(take_filepath, fbx_options)
                print(f"Exported: {take_filepath}")
            # Do this when export has failed!
            else:
                # The return will be none due to failer to export! This will cancel the for loop and the export.
                print(f"Take failed to export! Take name: {take_filepath}")
                return None                     
                 
    return True # Returning True will confirm export was successful
type or paste code here

I hope this helps! :slightly_smiling_face:

Thanks for sharing Elfis,

I actually figured the solution to the issues I was having being I needed to instantiate the application first and also pass in a single take rather than a list… My code went from:

selected_take = get_selected_take()
batch_options = FBBatchOptions()
selected_models = get_selected_models()
batch_options.OutputDirectory = r"E:\FBXExports"    
app = FBApplication()

FBApplication.FileExportBatch(app,"test", selected_take, batch_options, selected_models)

To this:

selected_take = get_selected_take()[0]
batch_options = FBBatchOptions()
selected_models = get_selected_models()
batch_options.OutputDirectory = r"E:\FBXExports"    
app = FBApplication()

app.FileExportBatch("test", selected_take, batch_options, selected_models)

I am now getting an export to FBX, but the export is only of blank transforms, so I have a new issue to understand and figure out.

I have ended up using a modified version of what you showed here Elfis and I have made some great progress with this thanks very much. I have hit a snag where I only want the joint hierarchy to be saved and I am not sure how I would go about that. Do you know how I can specify what is saved from the scene?

1 Like

Hey TheBeaver, Correct me if I’m wrong. You want to export (save) only the joint heirarchy with animation?

To get access to options for exporting you need to look at FBFbxOptions.

Examples below

        #Define lSystem
        lSystem = FBSystem()
        
        app = FBApplication()
        # Create an FBFbxOptions object initialized to save data.=========================
        fbx_options = FBFbxOptions(False)  # True if load/merge fbx file options and False for saving files!
        
        fbx_options.SetAll( FBElementAction.kFBElementActionDiscard, False ) # Sets all fbx options off
        # Below is setting  my skeleton, animation and character definition as on for saving option:
        fbx_options.Bones = FBElementAction.kFBElementActionSave
        fbx_options.BonesAnimation = True  
        fbx_options.Characters = FBElementAction.kFBElementActionSave 
        fbx_options.FileFormatAndVersion = FBFileFormatAndVersion.kFBFBX2014_2015 # FBX save version 2014_2015 
        fbx_options.EmbedMedia = False   # Dont save media files
        #fbx_options.Actors = FBElementAction.kFBElementActionSave  
        #===================================================================================

Hi Elfis, I still seem to be getting geometry with the save. I understand what the snippet is supposed to be doing. I have also noticed the animation is exporting but it is also stationary. Is there some sort of root motion option I am missing here?

The reason for this happening may be due a default take is within your scene that needs deleting. Check in the Naviagtor under Takes to see if you have additional tracks that are not used and remove these. A default take called take 001 is always present after merging animation. I delete this by code after merging multiple animations into a single MB scene.

I couldn’t see any default takes. All the takes I have should be there.

Hey The Beaver,

Did you get this fixed?

I’ve found one clean way to do this is to only open the file with the particular elements you need (its possible you’d need other elements such as Character depending on how you have the animation loaded onto the skeleton).

        app =  FBApplication()
        # To init the option for a load/merge: pass True as first param
        options = FBFbxOptions(True)
        # Tell that by default we won't load any scene elements at all
        options.SetAll(FBElementAction.kFBElementActionDiscard, False)
        # Tell that by default we won't load any Settings
        options.BaseCameras = False
        options.CameraSwitcherSettings = False
        options.CurrentCameraSettings = False
        options.GlobalLightingSettings = False
        options.TransportSettings = False
        # Except for the following
        options.Bones= FBElementAction.kFBElementActionAppend
        options.BonesAnimation= True
        # Open the fbx using those options
        app.FileOpen(fbx_file, False, options)