Community Tutorial: Command Line Rendering With Unreal Engine Movie Render Queue

Hi all,

I am pulling this information out of a colleague’s reply, but I think it is relevant to the challenges shared here. This isn’t a place where I can maintain a support thread, but I appreciate folks supporting others and feel this is relevant to those tackling the topic.

TLDR:

Unfortunately, the ObjectId pass is based on the internal “Hitproxy” pass which is used by the editor when clicking the mouse in the viewport to determine which object you’ve clicked on, so it requires the full editor. Because of this, it doesn’t work in “-game” mode - as a workaround, we suggest moving your render farm to use the full editor instead of -game. This will enable support for the ObjectID pass and you will get more consistency with renders that artists are doing using Render (Local), as it is the exact same code path as when they are using the user interface. It will increase the load times by a small amount but the repeatability (and more tested code paths) are worth it.

Details:

I have provided some sample code below on how to render queue assets using the full editor (but launched from a command line). It is a two-part solution - the first part is a “bootstrap” Python script that is run automatically after the editor finishes loading. This Python script does some additional work (waiting for the asset registry) and then it tells the editor to render with a custom Executor. The custom executor does some additional work that is normally done internally (when using -game mode) such as loading the queue asset specified on the Command Line. After doing this custom work it then creates a UMoviePipelinePIEExecutor and runs that - this is the same executor that is normally used when pressing Render (Local).

To get the Python script to run the console command after the editor launches you can use -execcmds=“pyCustomRenderBootstrap.py” and that Python file will be run on the first frame after the editor opens. Unfortunately at this point the Asset Registry is not fully loaded, so attempts to load a particular queue/sequence will fail, so your script will need to wait for the registry to finish and then proceed. Below is an example of CustomRenderBootstrap.py that registers a hook to check every frame if the registry is finished, and then once it is, proceeds. You will need to place this in your project’s Content/Python/ folder as CustomRenderBootstrap.py.

import unreal
import MyCustomEditorRenderExecutor
 
"""
This is a bootstrapping script that is executed when the editor starts in a
mode where it should connect read the command line and automatically render a job specified on the command line without artist intervention. It simply
calls render_queue_with_executor with a custom executor which then spawns
normal PIE executors - MyCustomEditorRenderExecutor is effectively a 'wrapper' around a PIE executor.
 
USAGE: UnrealEditor-Cmd.exe C:/Path/To/Project.uproject -execcmds="py CustomRenderBootstrap.py" -MoviePipelineConfig="/Game/Path/To/YourQueueAsset.YourQueueAsset"
 
The editor should launch, then automatically load the map specified by the first job in YourQueueAsset, then render it in PIE, then load the map for the next job, etc. Finally it will quit the editor on finish.
"""
 
tick_handle = None
custom_executor = None
 
def initialize_render_job():
    print('Initialize render job')
    
    # Create an instance of our custom executor
    global custom_executor
    custom_executor = MyCustomEditorRenderExecutor.MoviePipelineMyCustomEditorRenderExecutor()
    
    # Listen for the executor to be finished so we can request editor shutdown
    custom_executor.on_executor_finished_delegate.add_callable_unique(on_custom_executor_finished)
    
    # Now tell our custom executor to render which will load the queue asset and then create PIE executor instances.    
    subsystem = unreal.get_editor_subsystem(unreal.MoviePipelineQueueSubsystem)
    subsystem.render_queue_with_executor_instance(custom_executor)
 
def on_custom_executor_finished(executor, success):
    # Unfortunately the success bool isn't very useful at this time (errors report success)
    # so we can't do much with it here, but if you really need it you can get the correct
    # information from the individual job work callbacks on the PIE Executor and then you can
    # bubble that information up with another delegate, etc.
    unreal.log("Custom Executor Finished. Quitting editor now! Success: " + str(success))
    unreal.SystemLibrary.quit_editor()
    
def wait_for_asset_registry(delta_seconds):
    asset_registry = unreal.AssetRegistryHelpers.get_asset_registry()
    if asset_registry.is_loading_assets():
        unreal.log_warning("Still loading...")
        pass
    else:
        global tick_handle
        unreal.unregister_slate_pre_tick_callback(tick_handle)
        initialize_render_job()
 
 
# The asset registry may not be fully loaded by the time this is called, so we
# will wait until it has finished parsing all of the assets in the project
# before we move on, otherwise attempts to look assets up may fail
# unexpectedly. This registers an OnTick callback and it will get called once
# per frame. Once the registry reports that we're loaded then we'll start the
# render job and unregister from this callback so that we only try to start
# rendering once!
tick_handle = unreal.register_slate_pre_tick_callback(wait_for_asset_registry)

The second part of solving this issue is a custom executor. This custom executor is responsible for using the editor version of commands to load the appropriate map, then once loaded, it invokes the PIE Executor (which is what is run when you render with Render (Local)). I have attached the file as an attachment here due to its length.

In your /Content/Python folder add a file named “init_unreal.py” and inside of it add “import MyCustomEditorRenderExecutor” to the top. This is required because the MyCustomEditorRenderExecutor Python module contains a UClass implemented inside of Python, so it needs to be imported on startup for Unreal to recognize it and be able to create instances of it.

Once you do that you can drop the -MoviePipelineLocalExecutorClass, -MoviePipelineConfig, and -MoviePipelineClass arguments from the command arguments. I believe -Unattended still works as expected and -RenderOffscreen works as well (if you already have it working with -game mode)

Final usage would look something like this:

UnrealEditor-Cmd.exe C:/Path/To/Project.uproject -execcmds=“py CustomRenderBootstrap.py” -MoviePipelineConfig=“/Game/Path/To/YourQueueAsset.YourQueueAsset”

2 Likes