Skip to content
Guillaume Jobst edited this page Jul 26, 2024 · 10 revisions

Unreal PYthon Remote Execution

This module allows you do execute python code in Unreal, from another python process.

Code can be string, or file path. All the dependencies of the executed script must be available in Unreal environment.

Config

The module needs to fetch few settings from the python plugin in Unreal, such as multicast group, ip etc.

pysettings

This can be done either by creating a config object from a given .uproject path:

from upyrc import upyre
PROJECT_FOLDER = "D:/Projects/UpyreTest/UnrealRemoteControlTestData"
config = upyre.RemoteExecutionConfig.from_uproject_path(f"{PROJECT_FOLDER}/UpyreTest.uproject")

or you can create the config object manually:

config = upyre.RemoteExecutionConfig(buffer_size=0, multicast_group=(239.0.01, 6766), project_name='UpyreTest')

Connection

Once the config object is created, you can open a connection, the connection needs to be closed once you're done executing the python code. This can be done either with a context-like object, or opening / closing the connection manually:

with upyre.PythonRemoteConnection(config) as conn:
    ...
    # Python code execution here

conn =  upyre.PythonRemoteConnection(config)
conn.open_connection() # Connection opened with Unreal.
conn.close_connection() # Close the connection.

Code execution

Once the connection is opened, you can execute python code remotly, either as string or file path, with or without arguments.

# Create the connection
with upyre.PythonRemoteConnection(config) as conn:

    # This execute simple multiline python statement OR file
    result = conn.execute_python_command("unreal.log('Hello')\nunreal.log('World ?')", exec_type=upyre.ExecTypes.EXECUTE_FILE, raise_exc=True)
    print(result)

    # Example with a file path
    result_file = conn.execute_python_command(f"{PROJECT_FOLDER}/Content/Blueprints/test_pyfile_ue.py", exec_type=upyre.ExecTypes.EXECUTE_FILE)
    print(result_file)

    # Example with a file path and arguments
    result_file = conn.execute_python_command(f"{PROJECT_FOLDER}/Content/Blueprints/test_pyfile_ue.py hello world", exec_type=upyre.ExecTypes.EXECUTE_FILE)
    print(result_file)
    # Here the two arguments "hello" and "world" will be sent to the file before execution. And can be accessed with sys.argv list (the first entry being the file path).
    # >>> From cmd arguments: hello world.

    # You can also evalurate a single line statement and get the result back
    result_statement = conn.execute_python_command("1 + 2", exec_type=upyre.ExecTypes.EVALUATE_STATEMENT)
    print(result_statement.result) # This print 3

Helpers and template

The module can construct python code with the help of jinja2 templates. This allows to create more complex code, and to easily send variables, without the need to have everything set on Unreal side.

You can see few example of it with the helpers in place in the module, such as:

Logging:

# Print out some logs.
conn.log("I'm a message.")
conn.log_warning("Uho, I'm a warning...")
conn.log_error("Oh bummer I'm an error !!!")
# ---> In Unreal: Will print the logs in the "Output log" tab.

Or executing editor utility bp function or setting parameter on them:

# Set a property of a utility BP.
conn.set_bp_property("/Game/Blueprints/BPU_TestUtils.BPU_TestUtils_C", properties={"VarToChange":True})

# Run utility BP functions.
conn.execute_bp_method("/Game/Blueprints/BPU_TestUtils.BPU_TestUtils_C", "SimplePrint")
conn.execute_bp_method("/Game/Blueprints/BPU_TestUtils.BPU_TestUtils_C", "PrintWithArg", args=("Hello", 5), raise_exc=True)

The templates used for those helpers can be found in 📁upyrc\re_templates folder.

You can execute your own template file with this method:

# Execute custom template.
conn.execute_template_file(f"{PROJECT_FOLDER}/Content/Blueprints/test_template.jinja", template_kwargs={"msg":"Hello !"})
# ---> In Unreal: Will print "Im printed from a custom template ! Hello ! End of template" in the "Output log" tab.

More info on this method:

    def execute_template_file(self, file_path: Union[Path, str], template_kwargs: dict={},
                              search_paths: List[Union[Path, str]]=[],
                              timeout: float=5.0, raise_exc=True) -> PythonCommandResult:
        ''' Render and execute a given .jinja template file.
            An optionnal list of search paths can be given, to find other inherited templates.
        '''

Standard print data exchange

In the remote script (Unreal side) you can print out data starting with an identifier (XXX=) and fetch it back in the PythonCommandResult object:

# In the python command you execute Unreal side (my_command.py):
print("mydata=bar")

# In the remote python process (not Unreal).
with upyre.PythonRemoteConnection(config, open_json_output_pipe=True) as conn:
    cmd_result = conn.execute_python_command("my_command.py")
    cmd_result.get_first_output_with_identifier("mydata") # -> returns "bar"

This is a one way only data exchange, and only string data is supported atm. (But a json string can be sent that way)

JSON pipe data exchange

In order to be able to write and read data between processes ( Python Unreal and Remote Python process ), a json pipe is available. At the connection creation, a temporary json file is created, and data can be write in it by the connection itself, and the remote script executed on Unreal side as well.

⚠️ At the moment, the json file is shared by all commands executed from the same connection object. This behavior will change in the futur, to have data per command only, and one shared at the connection level.

See this graph to see more details:

image

To open the json pipe, just use the keyword argument:

with upyre.PythonRemoteConnection(config, open_json_output_pipe=True) as conn:
    ...

You can also open the pipe manually as such:

with upyre.PythonRemoteConnection(config) as conn:
    conn.init_json_pipe()

To write and read data from the connection object, use:

with upyre.PythonRemoteConnection(config, open_json_output_pipe=True) as conn:
    conn.write("data_entry", 555)
    data = conn.read("data_entry") # will return 555

    conn.flush() # Will flush the entier json file, the data won't be available anymore. Automatically done at the connection closure.

In the executed command, for instance, in a python file you want to execute on Unreal's side, you can use the json pipeline as such:

# my_script_.py

from upyre_json_pipe import json_pipe
#  => The import upyre_json_pipe  will fail is the json pipe wasn't opened !

# Write data to the json file.
json_pipe.write("test_data", 123)

# Data written at the connection level can be read as well.
data = json_pipe.read("data_entry") # will return 555

# ---------------------------------------------------------

# remote_session.py

import upyre

with upyre.PythonRemoteConnection(config, open_json_output_pipe=True) as conn:
    result = conn.execute_python_command(f"my_script_.py", exec_type=upyre.ExecTypes.EXECUTE_FILE)
    
    # The data can be fetched in the PythonCommandResult object:
    result.read("test_data") # => returns 123

    # Or in the Connection object directly:
    conn.read("test_data") # => returns 123

Clone this wiki locally