diff --git a/docs/send2ue/introduction/images/8.png b/docs/send2ue/introduction/images/8.png index ba17d84d..e69de2b8 100644 Binary files a/docs/send2ue/introduction/images/8.png and b/docs/send2ue/introduction/images/8.png differ diff --git a/docs/send2ue/introduction/quickstart.md b/docs/send2ue/introduction/quickstart.md index 43798299..797206b1 100644 --- a/docs/send2ue/introduction/quickstart.md +++ b/docs/send2ue/introduction/quickstart.md @@ -45,7 +45,7 @@ Once you have enabled the plugins and project settings, you'll be prompted to re ![7](./images/7.png) -Search for "python" and then enable remote execution. Now Send to Unreal will work with your new Unreal project. +Search for "python" and then enable `remote execution` and set the Multicast bind address to `0.0.0.0`. Now Send to Unreal will work with your new Unreal project. ![8](./images/8.png) diff --git a/send2ue/__init__.py b/send2ue/__init__.py index 2d0f0ffc..bf629638 100644 --- a/send2ue/__init__.py +++ b/send2ue/__init__.py @@ -25,13 +25,13 @@ modules = [ export, ingest, + settings, unreal, utilities, formatting, validations, dialog, file_browser, - settings, operators, properties, constants, diff --git a/send2ue/dependencies/remote_execution.py b/send2ue/dependencies/remote_execution.py index 1f80fb62..61026038 100644 --- a/send2ue/dependencies/remote_execution.py +++ b/send2ue/dependencies/remote_execution.py @@ -1,6 +1,5 @@ # Copyright Epic Games, Inc. All Rights Reserved. -import os import sys as _sys import json as _json import uuid as _uuid @@ -8,6 +7,7 @@ import socket as _socket import logging as _logging import threading as _threading +import bpy # Protocol constants (see PythonScriptRemoteExecution.cpp for the full protocol definition) _PROTOCOL_VERSION = 1 # Protocol version number @@ -22,10 +22,6 @@ _NODE_PING_SECONDS = 1 # Number of seconds to wait before sending another "ping" message to discover remote notes _NODE_TIMEOUT_SECONDS = 5 # Number of seconds to wait before timing out a remote node that was discovered via UDP and has stopped sending "pong" responses -DEFAULT_MULTICAST_TTL = 0 # Multicast TTL (0 is limited to the local host, 1 is limited to the local subnet) -DEFAULT_MULTICAST_GROUP_ENDPOINT = ('239.0.0.1', 6766) # The multicast group endpoint tuple that the UDP multicast socket should join (must match the "Multicast Group Endpoint" setting in the Python plugin) -DEFAULT_MULTICAST_BIND_ADDRESS = '127.0.0.1' # The adapter address that the UDP multicast socket should bind to, or 127.0.0.1 to bind to all adapters (must match the "Multicast Bind Address" setting in the Python plugin) -DEFAULT_COMMAND_ENDPOINT = ('127.0.0.1', 6776) # The endpoint tuple for the TCP command connection hosted by this client (that the remote client will connect to) DEFAULT_RECEIVE_BUFFER_SIZE = 8192 # The default receive buffer size # Execution modes (these must match the names given to LexToString for EPythonCommandExecutionMode in IPythonScriptPlugin.h) @@ -35,22 +31,34 @@ class RemoteExecutionConfig(object): ''' - Configuration data for establishing a remote connection with a UE4 instance running Python. + Configuration data for establishing a remote connection with a Unreal Editor instance running Python. ''' def __init__(self): - self.multicast_ttl = DEFAULT_MULTICAST_TTL - self.multicast_group_endpoint = DEFAULT_MULTICAST_GROUP_ENDPOINT - self.multicast_bind_address = DEFAULT_MULTICAST_BIND_ADDRESS - self.command_endpoint = DEFAULT_COMMAND_ENDPOINT + # The multicast group endpoint tuple that the UDP multicast socket should join (must match the "Multicast Group Endpoint" setting in the Python plugin) + self.multicast_ttl = bpy.context.preferences.addons["send2ue"].preferences.multicast_ttl + + # The multicast group endpoint tuple that the UDP multicast socket should join (must match the "Multicast Group Endpoint" setting in the Python plugin) + host, port = bpy.context.preferences.addons["send2ue"].preferences.multicast_group_endpoint.split(':') + self.multicast_group_endpoint = (host, int(port)) + + # The endpoint tuple for the TCP command connection hosted by this client (that the remote client will connect to) + host, port = bpy.context.preferences.addons["send2ue"].preferences.command_endpoint.split(':') + self.command_endpoint = (host, int(port)) + + # The adapter address that the UDP multicast socket should bind to, or 0.0.0.0 to bind to all adapters (must match the "Multicast Bind Address" setting in the Python plugin) + self.multicast_bind_address = host class RemoteExecution(object): ''' - A remote execution session. This class can discover remote "nodes" (UE4 instances running Python), and allow you to open a command channel to a particular instance. + A remote execution session. This class can discover remote "nodes" (Unreal Editor instances running Python), and allow you to open a command channel to a particular instance. Args: config (RemoteExecutionConfig): Configuration controlling the connection settings for this session. ''' - def __init__(self, config=RemoteExecutionConfig()): + def __init__(self, config=None): + if not config: + config = RemoteExecutionConfig() + self._config = config self._broadcast_connection = None self._command_connection = None @@ -59,23 +67,23 @@ def __init__(self, config=RemoteExecutionConfig()): @property def remote_nodes(self): ''' - Get the current set of discovered remote "nodes" (UE4 instances running Python). + Get the current set of discovered remote "nodes" (Unreal Editor instances running Python). Returns: - list: A list of dicts containg the node ID and the other data. + list: A list of dicts containing the node ID and the other data. ''' return self._broadcast_connection.remote_nodes if self._broadcast_connection else [] def start(self): ''' - Start the remote execution session. This will begin the discovey process for remote "nodes" (UE4 instances running Python). + Start the remote execution session. This will begin the discovery process for remote "nodes" (Unreal Editor instances running Python). ''' self._broadcast_connection = _RemoteExecutionBroadcastConnection(self._config, self._node_id) self._broadcast_connection.open() def stop(self): ''' - Stop the remote execution session. This will end the discovey process for remote "nodes" (UE4 instances running Python), and close any open command connection. + Stop the remote execution session. This will end the discovery process for remote "nodes" (Unreal Editor instances running Python), and close any open command connection. ''' self.close_command_connection() if self._broadcast_connection: @@ -93,7 +101,7 @@ def has_command_connection(self): def open_command_connection(self, remote_node_id): ''' - Open a command connection to the given remote "node" (a UE4 instance running Python), closing any command connection that may currently be open. + Open a command connection to the given remote "node" (a Unreal Editor instance running Python), closing any command connection that may currently be open. Args: remote_node_id (string): The ID of the remote node (this can be obtained by querying `remote_nodes`). @@ -129,7 +137,7 @@ def run_command(self, command, unattended=True, exec_mode=MODE_EXEC_FILE, raise_ class _RemoteExecutionNode(object): ''' - A discovered remote "node" (aka, a UE4 instance running Python). + A discovered remote "node" (aka, a Unreal Editor instance running Python). Args: data (dict): The data representing this node (from its "pong" reponse). @@ -153,7 +161,7 @@ def should_timeout(self, now=None): class _RemoteExecutionBroadcastNodes(object): ''' - A thread-safe set of remote execution "nodes" (UE4 instances running Python). + A thread-safe set of remote execution "nodes" (Unreal Editor instances running Python). ''' def __init__(self): self._remote_nodes = {} @@ -162,7 +170,7 @@ def __init__(self): @property def remote_nodes(self): ''' - Get the current set of discovered remote "nodes" (UE4 instances running Python). + Get the current set of discovered remote "nodes" (Unreal Editor instances running Python). Returns: list: A list of dicts containg the node ID and the other data. @@ -223,7 +231,7 @@ def __init__(self, config, node_id): @property def remote_nodes(self): ''' - Get the current set of discovered remote "nodes" (UE4 instances running Python). + Get the current set of discovered remote "nodes" (Unreal Editor instances running Python). Returns: list: A list of dicts containg the node ID and the other data. @@ -232,7 +240,7 @@ def remote_nodes(self): def open(self): ''' - Open the UDP based messaging and discovery connection. This will begin the discovey process for remote "nodes" (UE4 instances running Python). + Open the UDP based messaging and discovery connection. This will begin the discovey process for remote "nodes" (Unreal Editor instances running Python). ''' self._running = True self._last_ping = None @@ -242,7 +250,7 @@ def open(self): def close(self): ''' - Close the UDP based messaging and discovery connection. This will end the discovey process for remote "nodes" (UE4 instances running Python). + Close the UDP based messaging and discovery connection. This will end the discovey process for remote "nodes" (Unreal Editor instances running Python). ''' self._running = False if self._broadcast_listen_thread: @@ -380,7 +388,7 @@ class _RemoteExecutionCommandConnection(object): Args: config (RemoteExecutionConfig): Configuration controlling the connection settings. node_id (string): The ID of the local "node" (this session). - remote_node_id (string): The ID of the remote "node" (the UE4 instance running Python). + remote_node_id (string): The ID of the remote "node" (the Unreal Editor instance running Python). ''' def __init__(self, config, node_id, remote_node_id): self._config = config @@ -541,7 +549,7 @@ def to_json(self): if self.data: json_obj['data'] = self.data return _json.dumps(json_obj, ensure_ascii=False) - + def to_json_bytes(self): ''' Convert this message to its JSON representation as UTF-8 bytes. diff --git a/send2ue/dependencies/rpc/base_server.py b/send2ue/dependencies/rpc/base_server.py index 512189b4..53d7ffb2 100644 --- a/send2ue/dependencies/rpc/base_server.py +++ b/send2ue/dependencies/rpc/base_server.py @@ -35,7 +35,8 @@ def run_in_main_thread(callable_instance, *args): globals().pop(ERROR_VALUE_NAME, None) EXECUTION_QUEUE.put((callable_instance, args)) - for attempt in range(timeout * 10): + start_time = time.time() + while time.time() - start_time < timeout: if RETURN_VALUE_NAME in globals(): return globals().get(RETURN_VALUE_NAME) elif ERROR_VALUE_NAME in globals(): diff --git a/send2ue/dependencies/unreal.py b/send2ue/dependencies/unreal.py index 3655dae5..a56faf3a 100644 --- a/send2ue/dependencies/unreal.py +++ b/send2ue/dependencies/unreal.py @@ -31,6 +31,7 @@ default_imports=['import unreal'], remap_pairs=REMAP_PAIRS, ) + rpc_client = rpc.client.RPCClient(port=UNREAL_PORT) unreal_response = '' diff --git a/send2ue/properties.py b/send2ue/properties.py index 3b75cd1d..a65b7f3b 100644 --- a/send2ue/properties.py +++ b/send2ue/properties.py @@ -1,6 +1,7 @@ # Copyright Epic Games, Inc. All Rights Reserved. import os +import sys import uuid import bpy from .constants import ToolInfo, PathModes, Template @@ -16,6 +17,15 @@ class Send2UeAddonProperties: default=True, description=f"This automatically creates the pre-defined collection (Export)" ) + extensions_repo_path: bpy.props.StringProperty( + name="Extensions Repo Path", + default="", + description=( + "Set this path to the folder that contains your Send to Unreal python extensions. All extensions " + "in this folder will be automatically loaded" + ) + ) + # ------------- Remote Execution settings ------------------ rpc_response_timeout: bpy.props.IntProperty( name="RPC Response Timeout", default=60, @@ -27,14 +37,30 @@ class Send2UeAddonProperties: set=settings.set_rpc_response_timeout, get=settings.get_rpc_response_timeout ) - extensions_repo_path: bpy.props.StringProperty( - name="Extensions Repo Path", - default="", - description=( - "Set this path to the folder that contains your Send to Unreal python extensions. All extensions " - "in this folder will be automatically loaded" - ) + multicast_ttl: bpy.props.IntProperty( + name="Multicast TTL", + default=0, + description=( + "Limits packet propagation for multicast connections. 0 restricts to local computer, 1 restricts to " + "local network. Default '0'" ) + ) + multicast_group_endpoint: bpy.props.StringProperty( + name="Multicast Group Endpoint", + default="239.0.0.1:6766", + description=( + "The multicast group endpoint that the UDP multicast socket should join. Must match setting " + "in Unreal." + ) + ) + command_endpoint: bpy.props.StringProperty( + name="Command Endpoint", + default="127.0.0.1:6776" if sys.platform == 'win32' else "0.0.0.0:6776", + description=( + "IP for UDP multicast to bind to and TCP command connection hosted by this client. " + "Must match setting in Unreal." + ) + ) class Send2UeWindowMangerProperties(bpy.types.PropertyGroup): diff --git a/send2ue/ui/addon_preferences.py b/send2ue/ui/addon_preferences.py index 008b29d6..a7496259 100644 --- a/send2ue/ui/addon_preferences.py +++ b/send2ue/ui/addon_preferences.py @@ -23,6 +23,17 @@ def draw(self, context): row.label(text='RPC Response Timeout') row.prop(self, 'rpc_response_timeout', text='') row = self.layout.row() + + row.label(text="Multicast TTL") + row.prop(self, 'multicast_ttl', text='') + row = self.layout.row() + row.label(text="Multicast Group Endpoint") + row.prop(self, 'multicast_group_endpoint', text='') + row = self.layout.row() + row.label(text="Command Endpoint") + row.prop(self, 'command_endpoint', text='') + row = self.layout.row() + row.label(text='Extensions Repo Path:') row = self.layout.row() row = row.split(factor=0.95, align=True) diff --git a/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini b/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini index 82caa850..ad7e369c 100644 --- a/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini +++ b/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini @@ -44,7 +44,7 @@ ManualIPAddress= [/Script/PythonScriptPlugin.PythonScriptPluginSettings] bRemoteExecution=True -RemoteExecutionMulticastBindAddress=127.0.0.1 +RemoteExecutionMulticastBindAddress=0.0.0.0 [/Script/WindowsTargetPlatform.WindowsTargetSettings] DefaultGraphicsRHI=DefaultGraphicsRHI_Default